origami-docspring 2.2.0 → 2.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/examples/attachments/attachment.rb +7 -8
- data/examples/attachments/nested_document.rb +6 -5
- data/examples/encryption/encryption.rb +5 -4
- data/examples/events/events.rb +7 -6
- data/examples/flash/flash.rb +10 -9
- data/examples/forms/javascript.rb +14 -13
- data/examples/forms/xfa.rb +67 -66
- data/examples/javascript/hello_world.rb +6 -5
- data/examples/javascript/js_emulation.rb +26 -26
- data/examples/loop/goto.rb +12 -11
- data/examples/loop/named.rb +17 -16
- data/examples/signature/signature.rb +11 -11
- data/examples/uri/javascript.rb +25 -24
- data/examples/uri/open-uri.rb +5 -4
- data/examples/uri/submitform.rb +11 -10
- data/lib/origami/3d.rb +330 -334
- data/lib/origami/acroform.rb +267 -268
- data/lib/origami/actions.rb +266 -278
- data/lib/origami/annotations.rb +659 -670
- data/lib/origami/array.rb +192 -196
- data/lib/origami/boolean.rb +66 -70
- data/lib/origami/catalog.rb +360 -363
- data/lib/origami/collections.rb +132 -133
- data/lib/origami/compound.rb +125 -129
- data/lib/origami/destinations.rb +226 -237
- data/lib/origami/dictionary.rb +155 -154
- data/lib/origami/encryption.rb +967 -923
- data/lib/origami/extensions/fdf.rb +270 -275
- data/lib/origami/extensions/ppklite.rb +323 -328
- data/lib/origami/filespec.rb +170 -173
- data/lib/origami/filters/ascii.rb +162 -167
- data/lib/origami/filters/ccitt/tables.rb +248 -252
- data/lib/origami/filters/ccitt.rb +309 -312
- data/lib/origami/filters/crypt.rb +31 -34
- data/lib/origami/filters/dct.rb +47 -50
- data/lib/origami/filters/flate.rb +57 -60
- data/lib/origami/filters/jbig2.rb +50 -53
- data/lib/origami/filters/jpx.rb +40 -43
- data/lib/origami/filters/lzw.rb +151 -155
- data/lib/origami/filters/predictors.rb +250 -255
- data/lib/origami/filters/runlength.rb +111 -115
- data/lib/origami/filters.rb +319 -325
- data/lib/origami/font.rb +173 -177
- data/lib/origami/functions.rb +62 -66
- data/lib/origami/graphics/colors.rb +203 -208
- data/lib/origami/graphics/instruction.rb +79 -81
- data/lib/origami/graphics/path.rb +141 -144
- data/lib/origami/graphics/patterns.rb +156 -160
- data/lib/origami/graphics/render.rb +51 -47
- data/lib/origami/graphics/state.rb +144 -142
- data/lib/origami/graphics/text.rb +185 -188
- data/lib/origami/graphics/xobject.rb +818 -804
- data/lib/origami/graphics.rb +25 -26
- data/lib/origami/header.rb +63 -65
- data/lib/origami/javascript.rb +718 -651
- data/lib/origami/linearization.rb +284 -285
- data/lib/origami/metadata.rb +156 -135
- data/lib/origami/name.rb +98 -100
- data/lib/origami/null.rb +49 -51
- data/lib/origami/numeric.rb +133 -135
- data/lib/origami/obfuscation.rb +180 -182
- data/lib/origami/object.rb +634 -631
- data/lib/origami/optionalcontent.rb +147 -149
- data/lib/origami/outline.rb +46 -48
- data/lib/origami/outputintents.rb +76 -77
- data/lib/origami/page.rb +637 -596
- data/lib/origami/parser.rb +214 -221
- data/lib/origami/parsers/fdf.rb +44 -45
- data/lib/origami/parsers/pdf/lazy.rb +147 -154
- data/lib/origami/parsers/pdf/linear.rb +104 -109
- data/lib/origami/parsers/pdf.rb +109 -107
- data/lib/origami/parsers/ppklite.rb +44 -46
- data/lib/origami/pdf.rb +886 -896
- data/lib/origami/reference.rb +116 -120
- data/lib/origami/signature.rb +617 -625
- data/lib/origami/stream.rb +560 -558
- data/lib/origami/string.rb +366 -368
- data/lib/origami/template/patterns.rb +50 -52
- data/lib/origami/template/widgets.rb +111 -114
- data/lib/origami/trailer.rb +153 -157
- data/lib/origami/tree.rb +55 -57
- data/lib/origami/version.rb +19 -19
- data/lib/origami/webcapture.rb +87 -90
- data/lib/origami/xfa/config.rb +409 -414
- data/lib/origami/xfa/connectionset.rb +113 -117
- data/lib/origami/xfa/datasets.rb +38 -42
- data/lib/origami/xfa/localeset.rb +33 -37
- data/lib/origami/xfa/package.rb +49 -52
- data/lib/origami/xfa/pdf.rb +54 -59
- data/lib/origami/xfa/signature.rb +33 -37
- data/lib/origami/xfa/sourceset.rb +34 -38
- data/lib/origami/xfa/stylesheet.rb +35 -39
- data/lib/origami/xfa/template.rb +1630 -1634
- data/lib/origami/xfa/xdc.rb +33 -37
- data/lib/origami/xfa/xfa.rb +132 -123
- data/lib/origami/xfa/xfdf.rb +34 -38
- data/lib/origami/xfa/xmpmeta.rb +34 -38
- data/lib/origami/xfa.rb +50 -53
- data/lib/origami/xreftable.rb +462 -462
- data/lib/origami.rb +37 -38
- data/test/test_actions.rb +22 -20
- data/test/test_annotations.rb +54 -52
- data/test/test_forms.rb +23 -21
- data/test/test_native_types.rb +82 -78
- data/test/test_object_tree.rb +25 -24
- data/test/test_pages.rb +43 -41
- data/test/test_pdf.rb +2 -0
- data/test/test_pdf_attachment.rb +23 -21
- data/test/test_pdf_create.rb +16 -15
- data/test/test_pdf_encrypt.rb +69 -66
- data/test/test_pdf_parse.rb +131 -129
- data/test/test_pdf_parse_lazy.rb +53 -53
- data/test/test_pdf_sign.rb +67 -67
- data/test/test_streams.rb +145 -143
- data/test/test_xrefs.rb +46 -45
- metadata +64 -8
data/lib/origami/encryption.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# This file is part of Origami, PDF manipulation framework for Ruby
|
5
|
+
# Copyright (C) 2016 Guillaume Delugré.
|
6
|
+
#
|
7
|
+
# Origami is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# Origami is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
20
|
|
21
21
|
require 'openssl'
|
22
22
|
require 'securerandom'
|
@@ -24,1062 +24,1106 @@ require 'digest/md5'
|
|
24
24
|
require 'digest/sha2'
|
25
25
|
|
26
26
|
module Origami
|
27
|
+
class EncryptionError < Error # :nodoc:
|
28
|
+
end
|
29
|
+
|
30
|
+
class EncryptionInvalidPasswordError < EncryptionError # :nodoc:
|
31
|
+
end
|
32
|
+
|
33
|
+
class EncryptionNotSupportedError < EncryptionError # :nodoc:
|
34
|
+
end
|
27
35
|
|
28
|
-
|
36
|
+
class PDF
|
37
|
+
#
|
38
|
+
# Returns whether the PDF file is encrypted.
|
39
|
+
#
|
40
|
+
def encrypted?
|
41
|
+
trailer_key? :Encrypt
|
29
42
|
end
|
30
43
|
|
31
|
-
|
44
|
+
#
|
45
|
+
# Decrypts the current document.
|
46
|
+
# _passwd_:: The password to decrypt the document.
|
47
|
+
#
|
48
|
+
def decrypt(passwd = "")
|
49
|
+
raise EncryptionError, "PDF is not encrypted" unless encrypted?
|
50
|
+
|
51
|
+
# Turn the encryption dictionary into a standard encryption dictionary.
|
52
|
+
handler = trailer_key(:Encrypt)
|
53
|
+
handler = cast_object(handler.reference, Encryption::Standard::Dictionary)
|
54
|
+
|
55
|
+
unless handler.Filter == :Standard
|
56
|
+
raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter}'"
|
57
|
+
end
|
58
|
+
|
59
|
+
doc_id = trailer_key(:ID)
|
60
|
+
if doc_id.is_a?(Array)
|
61
|
+
doc_id = doc_id.first
|
62
|
+
else
|
63
|
+
raise EncryptionError, "Document ID was not found or is invalid" unless handler.V.to_i == 5
|
64
|
+
end
|
65
|
+
|
66
|
+
encryption_key = handler.derive_encryption_key(passwd, doc_id)
|
67
|
+
|
68
|
+
extend(Encryption::EncryptedDocument)
|
69
|
+
self.encryption_handler = handler
|
70
|
+
self.encryption_key = encryption_key
|
71
|
+
|
72
|
+
decrypt_objects
|
73
|
+
|
74
|
+
self
|
32
75
|
end
|
33
76
|
|
34
|
-
|
77
|
+
#
|
78
|
+
# Encrypts the current document with the provided passwords.
|
79
|
+
# The document will be encrypted at writing-on-disk time.
|
80
|
+
# _userpasswd_:: The user password.
|
81
|
+
# _ownerpasswd_:: The owner password.
|
82
|
+
# _options_:: A set of options to configure encryption.
|
83
|
+
#
|
84
|
+
def encrypt(options = {})
|
85
|
+
raise EncryptionError, "PDF is already encrypted" if encrypted?
|
86
|
+
|
87
|
+
#
|
88
|
+
# Default encryption options.
|
89
|
+
#
|
90
|
+
params =
|
91
|
+
{
|
92
|
+
user_passwd: '',
|
93
|
+
owner_passwd: '',
|
94
|
+
cipher: 'aes', # :RC4 or :AES
|
95
|
+
key_size: 128, # Key size in bits
|
96
|
+
hardened: false, # Use newer password validation (since Reader X)
|
97
|
+
encrypt_metadata: true, # Metadata shall be encrypted?
|
98
|
+
permissions: Encryption::Standard::Permissions::ALL # Document permissions
|
99
|
+
}.update(options)
|
100
|
+
|
101
|
+
# Get the cryptographic parameters.
|
102
|
+
version, revision = crypto_revision_from_options(params)
|
103
|
+
|
104
|
+
# Create the security handler.
|
105
|
+
handler, encryption_key = create_security_handler(version, revision, params)
|
106
|
+
|
107
|
+
# Turn this document into an EncryptedDocument instance.
|
108
|
+
extend(Encryption::EncryptedDocument)
|
109
|
+
self.encryption_handler = handler
|
110
|
+
self.encryption_key = encryption_key
|
111
|
+
|
112
|
+
self
|
35
113
|
end
|
36
114
|
|
37
|
-
|
115
|
+
private
|
38
116
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
117
|
+
#
|
118
|
+
# Installs the standard security dictionary, marking the document as being encrypted.
|
119
|
+
# Returns the handler and the encryption key used for protecting contents.
|
120
|
+
#
|
121
|
+
def create_security_handler(version, revision, params)
|
122
|
+
# Ensure the document has an ID.
|
123
|
+
doc_id = (trailer_key(:ID) || generate_id).first
|
124
|
+
|
125
|
+
# Create the standard encryption dictionary.
|
126
|
+
handler = Encryption::Standard::Dictionary.new
|
127
|
+
handler.Filter = :Standard
|
128
|
+
handler.V = version
|
129
|
+
handler.R = revision
|
130
|
+
handler.Length = params[:key_size]
|
131
|
+
handler.P = -1 # params[:Permissions]
|
132
|
+
|
133
|
+
# Build the crypt filter dictionary.
|
134
|
+
if revision >= 4
|
135
|
+
handler.EncryptMetadata = params[:encrypt_metadata]
|
136
|
+
handler.CF = Dictionary.new
|
137
|
+
crypt_filter = Encryption::CryptFilterDictionary.new
|
138
|
+
crypt_filter.AuthEvent = :DocOpen
|
139
|
+
|
140
|
+
crypt_filter.CFM = if revision == 4
|
141
|
+
:AESV2
|
142
|
+
else
|
143
|
+
:AESV3
|
44
144
|
end
|
45
145
|
|
46
|
-
|
47
|
-
# Decrypts the current document.
|
48
|
-
# _passwd_:: The password to decrypt the document.
|
49
|
-
#
|
50
|
-
def decrypt(passwd = "")
|
51
|
-
raise EncryptionError, "PDF is not encrypted" unless self.encrypted?
|
146
|
+
crypt_filter.Length = params[:key_size] >> 3
|
52
147
|
|
53
|
-
|
54
|
-
|
55
|
-
|
148
|
+
handler.CF[:StdCF] = crypt_filter
|
149
|
+
handler.StmF = handler.StrF = :StdCF
|
150
|
+
end
|
56
151
|
|
57
|
-
|
58
|
-
raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter}'"
|
59
|
-
end
|
152
|
+
user_passwd, owner_passwd = params[:user_passwd], params[:owner_passwd]
|
60
153
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
doc_id = doc_id.first
|
66
|
-
end
|
154
|
+
# Setup keys.
|
155
|
+
handler.set_passwords(owner_passwd, user_passwd, doc_id)
|
156
|
+
encryption_key = handler.compute_user_encryption_key(user_passwd, doc_id)
|
67
157
|
|
68
|
-
|
158
|
+
# Install the encryption dictionary to the document.
|
159
|
+
trailer.Encrypt = self << handler
|
69
160
|
|
70
|
-
|
71
|
-
|
72
|
-
self.encryption_key = encryption_key
|
161
|
+
[handler, encryption_key]
|
162
|
+
end
|
73
163
|
|
74
|
-
|
164
|
+
#
|
165
|
+
# Converts the parameters passed to PDF#encrypt.
|
166
|
+
# Returns [ version, revision, crypt_filters ]
|
167
|
+
#
|
168
|
+
def crypto_revision_from_options(params)
|
169
|
+
case params[:cipher].upcase
|
170
|
+
when 'RC4'
|
171
|
+
crypto_revision_from_rc4_key(params[:key_size])
|
172
|
+
when 'AES'
|
173
|
+
crypto_revision_from_aes_key(params[:key_size], params[:hardened])
|
174
|
+
else
|
175
|
+
raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
|
176
|
+
end
|
177
|
+
end
|
75
178
|
|
76
|
-
|
77
|
-
|
179
|
+
#
|
180
|
+
# Compute the required standard security handler version based on the RC4 key size.
|
181
|
+
# _key_size_:: Key size in bits.
|
182
|
+
# Returns [ version, revision ].
|
183
|
+
#
|
184
|
+
def crypto_revision_from_rc4_key(key_size)
|
185
|
+
raise EncryptionError, "Invalid RC4 key length" unless key_size.between?(40, 128) && (key_size % 8 == 0)
|
186
|
+
|
187
|
+
if key_size > 40
|
188
|
+
version = 2
|
189
|
+
revision = 3
|
190
|
+
else
|
191
|
+
version = 1
|
192
|
+
revision = 2
|
193
|
+
end
|
194
|
+
|
195
|
+
[version, revision]
|
196
|
+
end
|
78
197
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
:user_passwd => '',
|
95
|
-
:owner_passwd => '',
|
96
|
-
:cipher => 'aes', # :RC4 or :AES
|
97
|
-
:key_size => 128, # Key size in bits
|
98
|
-
:hardened => false, # Use newer password validation (since Reader X)
|
99
|
-
:encrypt_metadata => true, # Metadata shall be encrypted?
|
100
|
-
:permissions => Encryption::Standard::Permissions::ALL # Document permissions
|
101
|
-
}.update(options)
|
102
|
-
|
103
|
-
# Get the cryptographic parameters.
|
104
|
-
version, revision = crypto_revision_from_options(params)
|
105
|
-
|
106
|
-
# Create the security handler.
|
107
|
-
handler, encryption_key = create_security_handler(version, revision, params)
|
108
|
-
|
109
|
-
# Turn this document into an EncryptedDocument instance.
|
110
|
-
self.extend(Encryption::EncryptedDocument)
|
111
|
-
self.encryption_handler = handler
|
112
|
-
self.encryption_key = encryption_key
|
113
|
-
|
114
|
-
self
|
198
|
+
#
|
199
|
+
# Compute the required standard security handler version based on the AES key size.
|
200
|
+
# _key_size_:: Key size in bits.
|
201
|
+
# _hardened_:: Use the extension level 8 hardened derivation algorithm.
|
202
|
+
# Returns [ version, revision ].
|
203
|
+
#
|
204
|
+
def crypto_revision_from_aes_key(key_size, hardened)
|
205
|
+
if key_size == 128
|
206
|
+
version = revision = 4
|
207
|
+
elsif key_size == 256
|
208
|
+
version = 5
|
209
|
+
revision = if hardened
|
210
|
+
6
|
211
|
+
else
|
212
|
+
5
|
115
213
|
end
|
214
|
+
else
|
215
|
+
raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
|
216
|
+
end
|
116
217
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
# Installs the standard security dictionary, marking the document as being encrypted.
|
121
|
-
# Returns the handler and the encryption key used for protecting contents.
|
122
|
-
#
|
123
|
-
def create_security_handler(version, revision, params)
|
124
|
-
|
125
|
-
# Ensure the document has an ID.
|
126
|
-
doc_id = (trailer_key(:ID) || generate_id).first
|
127
|
-
|
128
|
-
# Create the standard encryption dictionary.
|
129
|
-
handler = Encryption::Standard::Dictionary.new
|
130
|
-
handler.Filter = :Standard
|
131
|
-
handler.V = version
|
132
|
-
handler.R = revision
|
133
|
-
handler.Length = params[:key_size]
|
134
|
-
handler.P = -1 # params[:Permissions]
|
135
|
-
|
136
|
-
# Build the crypt filter dictionary.
|
137
|
-
if revision >= 4
|
138
|
-
handler.EncryptMetadata = params[:encrypt_metadata]
|
139
|
-
handler.CF = Dictionary.new
|
140
|
-
crypt_filter = Encryption::CryptFilterDictionary.new
|
141
|
-
crypt_filter.AuthEvent = :DocOpen
|
142
|
-
|
143
|
-
if revision == 4
|
144
|
-
crypt_filter.CFM = :AESV2
|
145
|
-
else
|
146
|
-
crypt_filter.CFM = :AESV3
|
147
|
-
end
|
148
|
-
|
149
|
-
crypt_filter.Length = params[:key_size] >> 3
|
150
|
-
|
151
|
-
handler.CF[:StdCF] = crypt_filter
|
152
|
-
handler.StmF = handler.StrF = :StdCF
|
153
|
-
end
|
154
|
-
|
155
|
-
user_passwd, owner_passwd = params[:user_passwd], params[:owner_passwd]
|
218
|
+
[version, revision]
|
219
|
+
end
|
220
|
+
end
|
156
221
|
|
157
|
-
|
158
|
-
|
159
|
-
|
222
|
+
#
|
223
|
+
# Module to provide support for encrypting and decrypting PDF documents.
|
224
|
+
#
|
225
|
+
module Encryption
|
226
|
+
#
|
227
|
+
# Generates _n_ random bytes from a fast PRNG.
|
228
|
+
#
|
229
|
+
def self.rand_bytes(n)
|
230
|
+
Random.new.bytes(n)
|
231
|
+
end
|
160
232
|
|
161
|
-
|
162
|
-
|
233
|
+
#
|
234
|
+
# Generates _n_ random bytes from a crypto PRNG.
|
235
|
+
#
|
236
|
+
def self.strong_rand_bytes(n)
|
237
|
+
SecureRandom.random_bytes(n)
|
238
|
+
end
|
163
239
|
|
164
|
-
|
240
|
+
module EncryptedDocument
|
241
|
+
attr_accessor :encryption_key
|
242
|
+
attr_accessor :encryption_handler
|
243
|
+
|
244
|
+
# Get the encryption cipher from the crypt filter name.
|
245
|
+
def encryption_cipher(name)
|
246
|
+
@encryption_handler.encryption_cipher(name)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Get the default string encryption cipher.
|
250
|
+
def string_encryption_cipher
|
251
|
+
@encryption_handler.string_encryption_cipher
|
252
|
+
end
|
253
|
+
|
254
|
+
# Get the default stream encryption cipher.
|
255
|
+
def stream_encryption_cipher
|
256
|
+
@encryption_handler.stream_encryption_cipher
|
257
|
+
end
|
258
|
+
|
259
|
+
private
|
260
|
+
|
261
|
+
#
|
262
|
+
# For each object subject to encryption, convert it to an EncryptedObject and decrypt it if necessary.
|
263
|
+
#
|
264
|
+
def decrypt_objects
|
265
|
+
each_encryptable_object do |object|
|
266
|
+
case object
|
267
|
+
when String
|
268
|
+
object.extend(EncryptedString) unless object.is_a?(EncryptedString)
|
269
|
+
object.decrypt!
|
270
|
+
|
271
|
+
when Stream
|
272
|
+
object.extend(EncryptedStream) unless object.is_a?(EncryptedStream)
|
273
|
+
end
|
165
274
|
end
|
275
|
+
end
|
276
|
+
|
277
|
+
#
|
278
|
+
# For each object subject to encryption, convert it to an EncryptedObject and mark it as not encrypted yet.
|
279
|
+
#
|
280
|
+
def encrypt_objects
|
281
|
+
each_encryptable_object do |object|
|
282
|
+
case object
|
283
|
+
when String
|
284
|
+
unless object.is_a?(EncryptedString)
|
285
|
+
object.extend(EncryptedString)
|
286
|
+
object.decrypted = true
|
287
|
+
end
|
166
288
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
def crypto_revision_from_options(params)
|
172
|
-
case params[:cipher].upcase
|
173
|
-
when 'RC4'
|
174
|
-
crypto_revision_from_rc4_key(params[:key_size])
|
175
|
-
when 'AES'
|
176
|
-
crypto_revision_from_aes_key(params[:key_size], params[:hardened])
|
177
|
-
else
|
178
|
-
raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
|
289
|
+
when Stream
|
290
|
+
unless object.is_a?(EncryptedStream)
|
291
|
+
object.extend(EncryptedStream)
|
292
|
+
object.decrypted = true
|
179
293
|
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
#
|
299
|
+
# Iterates over each encryptable objects in the document.
|
300
|
+
#
|
301
|
+
def each_encryptable_object(&b)
|
302
|
+
# Metadata may not be encrypted depending on the security handler configuration.
|
303
|
+
encrypt_metadata = (@encryption_handler.EncryptMetadata != false)
|
304
|
+
metadata = self.Catalog.Metadata
|
305
|
+
|
306
|
+
each_object(recursive: true)
|
307
|
+
.lazy
|
308
|
+
.select { |object|
|
309
|
+
case object
|
310
|
+
when Stream
|
311
|
+
!object.is_a?(XRefStream) or (encrypt_metadata and object.equal?(metadata))
|
312
|
+
when String
|
313
|
+
!object.parent.equal?(@encryption_handler)
|
314
|
+
end
|
315
|
+
}
|
316
|
+
.each(&b)
|
317
|
+
end
|
318
|
+
|
319
|
+
def physicalize(options = {})
|
320
|
+
encrypt_objects
|
321
|
+
|
322
|
+
super
|
323
|
+
|
324
|
+
# remove encrypt dictionary if requested
|
325
|
+
if options[:decrypt]
|
326
|
+
delete_object(trailer[:Encrypt])
|
327
|
+
trailer[:Encrypt] = nil
|
180
328
|
end
|
181
329
|
|
182
|
-
|
183
|
-
|
184
|
-
# _key_size_:: Key size in bits.
|
185
|
-
# Returns [ version, revision ].
|
186
|
-
#
|
187
|
-
def crypto_revision_from_rc4_key(key_size)
|
188
|
-
raise EncryptionError, "Invalid RC4 key length" unless key_size.between?(40, 128) and key_size % 8 == 0
|
330
|
+
self
|
331
|
+
end
|
189
332
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
333
|
+
def build_object(object, revision, options)
|
334
|
+
if object.is_a?(EncryptedObject) && options[:decrypt]
|
335
|
+
object.pre_build
|
336
|
+
object.decrypt!
|
337
|
+
object.decrypted = false # makes it believe no encryption pass is required
|
338
|
+
object.post_build
|
197
339
|
|
198
|
-
|
340
|
+
return
|
199
341
|
end
|
200
342
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
343
|
+
super
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
#
|
348
|
+
# Module for encrypted PDF objects.
|
349
|
+
#
|
350
|
+
module EncryptedObject # :nodoc
|
351
|
+
attr_accessor :decrypted
|
352
|
+
|
353
|
+
def post_build
|
354
|
+
encrypt!
|
355
|
+
|
356
|
+
super
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
|
361
|
+
def compute_object_key(cipher)
|
362
|
+
doc = document
|
363
|
+
raise EncryptionError, "Document is not encrypted" unless doc.is_a?(EncryptedDocument)
|
364
|
+
|
365
|
+
encryption_key = doc.encryption_key
|
220
366
|
|
221
|
-
|
367
|
+
if doc.encryption_handler.V < 5
|
368
|
+
parent = indirect_parent
|
369
|
+
no, gen = parent.no, parent.generation
|
370
|
+
k = encryption_key + [no].pack("I")[0..2] + [gen].pack("I")[0..1]
|
371
|
+
|
372
|
+
key_len = [k.length, 16].min
|
373
|
+
k << "sAlT" if cipher == Encryption::AES
|
374
|
+
|
375
|
+
Digest::MD5.digest(k)[0, key_len]
|
376
|
+
else
|
377
|
+
encryption_key
|
222
378
|
end
|
379
|
+
end
|
223
380
|
end
|
224
381
|
|
225
382
|
#
|
226
|
-
# Module
|
383
|
+
# Module for encrypted String.
|
227
384
|
#
|
228
|
-
module
|
385
|
+
module EncryptedString
|
386
|
+
include EncryptedObject
|
229
387
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
def self.rand_bytes(n)
|
234
|
-
Random.new.bytes(n)
|
235
|
-
end
|
388
|
+
def self.extended(obj)
|
389
|
+
obj.decrypted = false
|
390
|
+
end
|
236
391
|
|
237
|
-
|
238
|
-
|
239
|
-
#
|
240
|
-
def self.strong_rand_bytes(n)
|
241
|
-
SecureRandom.random_bytes(n)
|
242
|
-
end
|
392
|
+
def encrypt!
|
393
|
+
return self unless @decrypted
|
243
394
|
|
244
|
-
|
245
|
-
|
246
|
-
attr_accessor :encryption_handler
|
395
|
+
cipher = document.string_encryption_cipher
|
396
|
+
raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
|
247
397
|
|
248
|
-
|
249
|
-
def encryption_cipher(name)
|
250
|
-
@encryption_handler.encryption_cipher(name)
|
251
|
-
end
|
398
|
+
key = compute_object_key(cipher)
|
252
399
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
400
|
+
encrypted_data =
|
401
|
+
if (cipher == RC4) || (cipher == Identity)
|
402
|
+
cipher.encrypt(key, value)
|
403
|
+
else
|
404
|
+
iv = Encryption.rand_bytes(AES::BLOCKSIZE)
|
405
|
+
cipher.encrypt(key, iv, value)
|
406
|
+
end
|
257
407
|
|
258
|
-
|
259
|
-
def stream_encryption_cipher
|
260
|
-
@encryption_handler.stream_encryption_cipher
|
261
|
-
end
|
408
|
+
@decrypted = false
|
262
409
|
|
263
|
-
|
264
|
-
|
265
|
-
#
|
266
|
-
# For each object subject to encryption, convert it to an EncryptedObject and decrypt it if necessary.
|
267
|
-
#
|
268
|
-
def decrypt_objects
|
269
|
-
each_encryptable_object do |object|
|
270
|
-
case object
|
271
|
-
when String
|
272
|
-
object.extend(EncryptedString) unless object.is_a?(EncryptedString)
|
273
|
-
object.decrypt!
|
274
|
-
|
275
|
-
when Stream
|
276
|
-
object.extend(EncryptedStream) unless object.is_a?(EncryptedStream)
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
410
|
+
replace(encrypted_data)
|
411
|
+
freeze
|
280
412
|
|
281
|
-
|
282
|
-
|
283
|
-
#
|
284
|
-
def encrypt_objects
|
285
|
-
each_encryptable_object do |object|
|
286
|
-
case object
|
287
|
-
when String
|
288
|
-
unless object.is_a?(EncryptedString)
|
289
|
-
object.extend(EncryptedString)
|
290
|
-
object.decrypted = true
|
291
|
-
end
|
292
|
-
|
293
|
-
when Stream
|
294
|
-
unless object.is_a?(EncryptedStream)
|
295
|
-
object.extend(EncryptedStream)
|
296
|
-
object.decrypted = true
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
end
|
413
|
+
self
|
414
|
+
end
|
301
415
|
|
302
|
-
|
303
|
-
|
304
|
-
#
|
305
|
-
def each_encryptable_object(&b)
|
306
|
-
|
307
|
-
# Metadata may not be encrypted depending on the security handler configuration.
|
308
|
-
encrypt_metadata = (@encryption_handler.EncryptMetadata != false)
|
309
|
-
metadata = self.Catalog.Metadata
|
310
|
-
|
311
|
-
self.each_object(recursive: true)
|
312
|
-
.lazy
|
313
|
-
.select { |object|
|
314
|
-
case object
|
315
|
-
when Stream
|
316
|
-
not object.is_a?(XRefStream) or (encrypt_metadata and object.equal?(metadata))
|
317
|
-
when String
|
318
|
-
not object.parent.equal?(@encryption_handler)
|
319
|
-
end
|
320
|
-
}
|
321
|
-
.each(&b)
|
322
|
-
end
|
416
|
+
def decrypt!
|
417
|
+
return self if @decrypted
|
323
418
|
|
324
|
-
|
325
|
-
|
419
|
+
cipher = document.string_encryption_cipher
|
420
|
+
raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
|
326
421
|
|
327
|
-
|
422
|
+
key = compute_object_key(cipher)
|
328
423
|
|
329
|
-
|
330
|
-
|
331
|
-
delete_object(self.trailer[:Encrypt])
|
332
|
-
self.trailer[:Encrypt] = nil
|
333
|
-
end
|
424
|
+
replace(cipher.decrypt(key, to_str))
|
425
|
+
@decrypted = true
|
334
426
|
|
335
|
-
|
336
|
-
|
427
|
+
self
|
428
|
+
end
|
429
|
+
end
|
337
430
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
object.post_build
|
431
|
+
#
|
432
|
+
# Module for encrypted Stream.
|
433
|
+
#
|
434
|
+
module EncryptedStream
|
435
|
+
include EncryptedObject
|
344
436
|
|
345
|
-
|
346
|
-
|
437
|
+
def self.extended(obj)
|
438
|
+
obj.decrypted = false
|
439
|
+
end
|
347
440
|
|
348
|
-
|
349
|
-
|
350
|
-
end
|
441
|
+
def encrypt!
|
442
|
+
return self unless @decrypted
|
351
443
|
|
352
|
-
|
353
|
-
# Module for encrypted PDF objects.
|
354
|
-
#
|
355
|
-
module EncryptedObject #:nodoc
|
356
|
-
attr_accessor :decrypted
|
444
|
+
encode!
|
357
445
|
|
358
|
-
|
359
|
-
|
446
|
+
cipher = get_encryption_cipher
|
447
|
+
key = compute_object_key(cipher)
|
360
448
|
|
361
|
-
|
362
|
-
|
449
|
+
@encoded_data =
|
450
|
+
if (cipher == RC4) || (cipher == Identity)
|
451
|
+
cipher.encrypt(key, encoded_data)
|
452
|
+
else
|
453
|
+
iv = Encryption.rand_bytes(AES::BLOCKSIZE)
|
454
|
+
cipher.encrypt(key, iv, @encoded_data)
|
455
|
+
end
|
363
456
|
|
364
|
-
|
457
|
+
@decrypted = false
|
365
458
|
|
366
|
-
|
367
|
-
|
368
|
-
raise EncryptionError, "Document is not encrypted" unless doc.is_a?(EncryptedDocument)
|
459
|
+
@encoded_data.freeze
|
460
|
+
freeze
|
369
461
|
|
370
|
-
|
462
|
+
self
|
463
|
+
end
|
371
464
|
|
372
|
-
|
373
|
-
|
374
|
-
no, gen = parent.no, parent.generation
|
375
|
-
k = encryption_key + [no].pack("I")[0..2] + [gen].pack("I")[0..1]
|
465
|
+
def decrypt!
|
466
|
+
return self if @decrypted
|
376
467
|
|
377
|
-
|
378
|
-
|
468
|
+
cipher = get_encryption_cipher
|
469
|
+
key = compute_object_key(cipher)
|
379
470
|
|
380
|
-
|
381
|
-
|
382
|
-
encryption_key
|
383
|
-
end
|
384
|
-
end
|
385
|
-
end
|
471
|
+
self.encoded_data = cipher.decrypt(key, @encoded_data)
|
472
|
+
@decrypted = true
|
386
473
|
|
387
|
-
|
388
|
-
|
389
|
-
#
|
390
|
-
module EncryptedString
|
391
|
-
include EncryptedObject
|
474
|
+
self
|
475
|
+
end
|
392
476
|
|
393
|
-
|
394
|
-
obj.decrypted = false
|
395
|
-
end
|
477
|
+
private
|
396
478
|
|
397
|
-
|
398
|
-
|
479
|
+
#
|
480
|
+
# Get the stream encryption cipher.
|
481
|
+
# The cipher used may depend on the presence of a Crypt filter.
|
482
|
+
#
|
483
|
+
def get_encryption_cipher
|
484
|
+
if filters.first == :Crypt
|
485
|
+
params = decode_params.first
|
399
486
|
|
400
|
-
|
401
|
-
|
487
|
+
crypt_filter = if params.is_a?(Dictionary) && params.Name.is_a?(Name)
|
488
|
+
params.Name.value
|
489
|
+
else
|
490
|
+
:Identity
|
491
|
+
end
|
402
492
|
|
403
|
-
|
493
|
+
cipher = document.encryption_cipher(crypt_filter)
|
494
|
+
else
|
495
|
+
cipher = document.stream_encryption_cipher
|
496
|
+
end
|
404
497
|
|
405
|
-
|
406
|
-
if cipher == RC4 or cipher == Identity
|
407
|
-
cipher.encrypt(key, self.value)
|
408
|
-
else
|
409
|
-
iv = Encryption.rand_bytes(AES::BLOCKSIZE)
|
410
|
-
cipher.encrypt(key, iv, self.value)
|
411
|
-
end
|
498
|
+
raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
|
412
499
|
|
413
|
-
|
500
|
+
cipher
|
501
|
+
end
|
502
|
+
end
|
414
503
|
|
415
|
-
|
416
|
-
|
504
|
+
#
|
505
|
+
# Identity transformation.
|
506
|
+
#
|
507
|
+
module Identity
|
508
|
+
def self.encrypt(_key, data)
|
509
|
+
data
|
510
|
+
end
|
511
|
+
|
512
|
+
def self.decrypt(_key, data)
|
513
|
+
data
|
514
|
+
end
|
515
|
+
end
|
417
516
|
|
418
|
-
|
517
|
+
#
|
518
|
+
# Class wrapper for the RC4 algorithm.
|
519
|
+
#
|
520
|
+
class RC4
|
521
|
+
#
|
522
|
+
# Encrypts data using the given key
|
523
|
+
#
|
524
|
+
def self.encrypt(key, data)
|
525
|
+
RC4.new(key).encrypt(data)
|
526
|
+
end
|
527
|
+
|
528
|
+
#
|
529
|
+
# Decrypts data using the given key
|
530
|
+
#
|
531
|
+
def self.decrypt(key, data)
|
532
|
+
RC4.new(key).decrypt(data)
|
533
|
+
end
|
534
|
+
|
535
|
+
#
|
536
|
+
# Creates and initialises a new RC4 generator using given key
|
537
|
+
#
|
538
|
+
def initialize(key)
|
539
|
+
@key = key
|
540
|
+
end
|
541
|
+
|
542
|
+
#
|
543
|
+
# Encrypt/decrypt data with the RC4 encryption algorithm
|
544
|
+
# RC4 is part of the PDF specification, so we need to support it
|
545
|
+
#
|
546
|
+
def cipher(data)
|
547
|
+
return +'' if data.empty?
|
548
|
+
|
549
|
+
# Enable RC4 cipher in OpenSSL security level
|
550
|
+
# This is needed for PDF compatibility as per PDF 1.7 specification
|
551
|
+
begin
|
552
|
+
# Try to lower security level temporarily to enable legacy ciphers
|
553
|
+
original_security_level = begin
|
554
|
+
OpenSSL::SSL.send(:security_level)
|
555
|
+
rescue
|
556
|
+
nil
|
557
|
+
end
|
558
|
+
if original_security_level
|
559
|
+
begin
|
560
|
+
OpenSSL::SSL.send(:security_level=, 0)
|
561
|
+
rescue
|
562
|
+
nil
|
419
563
|
end
|
564
|
+
end
|
565
|
+
|
566
|
+
# Now try to use RC4
|
567
|
+
rc4 = OpenSSL::Cipher.new('rc4').encrypt
|
568
|
+
rc4.key_len = @key.length
|
569
|
+
rc4.key = @key
|
570
|
+
result = rc4.update(data) + rc4.final
|
571
|
+
|
572
|
+
# Restore security level
|
573
|
+
if original_security_level
|
574
|
+
begin
|
575
|
+
OpenSSL::SSL.send(:security_level=, original_security_level)
|
576
|
+
rescue
|
577
|
+
nil
|
578
|
+
end
|
579
|
+
end
|
420
580
|
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
|
581
|
+
return result
|
582
|
+
rescue
|
583
|
+
# If we can't use OpenSSL's RC4 for any reason, silently fall back to pure Ruby implementation
|
584
|
+
end
|
426
585
|
|
427
|
-
|
586
|
+
# Pure Ruby RC4 implementation as fallback
|
587
|
+
s = (0..255).to_a
|
588
|
+
key_bytes = @key.bytes
|
589
|
+
key_length = key_bytes.length
|
590
|
+
j = 0
|
428
591
|
|
429
|
-
|
430
|
-
|
592
|
+
# Key-scheduling algorithm (KSA)
|
593
|
+
256.times do |i|
|
594
|
+
j = (j + s[i] + key_bytes[i % key_length]) % 256
|
595
|
+
s[i], s[j] = s[j], s[i]
|
596
|
+
end
|
431
597
|
|
432
|
-
|
433
|
-
|
598
|
+
# Pseudo-random generation algorithm (PRGA)
|
599
|
+
result = +""
|
600
|
+
i = j = 0
|
601
|
+
data.each_byte do |byte|
|
602
|
+
i = (i + 1) % 256
|
603
|
+
j = (j + s[i]) % 256
|
604
|
+
s[i], s[j] = s[j], s[i]
|
605
|
+
k = s[(s[i] + s[j]) % 256]
|
606
|
+
result << (byte ^ k).chr
|
434
607
|
end
|
435
608
|
|
436
|
-
|
437
|
-
|
438
|
-
#
|
439
|
-
module EncryptedStream
|
440
|
-
include EncryptedObject
|
609
|
+
result
|
610
|
+
end
|
441
611
|
|
442
|
-
|
443
|
-
|
444
|
-
|
612
|
+
alias_method :encrypt, :cipher
|
613
|
+
alias_method :decrypt, :cipher
|
614
|
+
end
|
445
615
|
|
446
|
-
|
447
|
-
|
616
|
+
#
|
617
|
+
# Class wrapper for AES mode CBC.
|
618
|
+
#
|
619
|
+
class AES
|
620
|
+
BLOCKSIZE = 16
|
448
621
|
|
449
|
-
|
622
|
+
attr_writer :iv
|
450
623
|
|
451
|
-
|
452
|
-
|
624
|
+
def self.encrypt(key, iv, data)
|
625
|
+
AES.new(key, iv).encrypt(data)
|
626
|
+
end
|
453
627
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
else
|
458
|
-
iv = Encryption.rand_bytes(AES::BLOCKSIZE)
|
459
|
-
cipher.encrypt(key, iv, @encoded_data)
|
460
|
-
end
|
628
|
+
def self.decrypt(key, data)
|
629
|
+
AES.new(key, nil).decrypt(data)
|
630
|
+
end
|
461
631
|
|
462
|
-
|
632
|
+
def initialize(key, iv, use_padding = true)
|
633
|
+
unless [16, 24, 32].include?(key.size)
|
634
|
+
raise EncryptionError, "Key must have a length of 128, 192 or 256 bits."
|
635
|
+
end
|
463
636
|
|
464
|
-
|
465
|
-
|
637
|
+
if !iv.nil? && (iv.size != BLOCKSIZE)
|
638
|
+
raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
|
639
|
+
end
|
466
640
|
|
467
|
-
|
468
|
-
|
641
|
+
@key = key
|
642
|
+
@iv = iv
|
643
|
+
@use_padding = use_padding
|
644
|
+
end
|
469
645
|
|
470
|
-
|
471
|
-
|
646
|
+
def encrypt(data)
|
647
|
+
if @iv.nil?
|
648
|
+
raise EncryptionError, "No initialization vector has been set."
|
649
|
+
end
|
472
650
|
|
473
|
-
|
474
|
-
|
651
|
+
if @use_padding
|
652
|
+
padlen = BLOCKSIZE - (data.size % BLOCKSIZE)
|
653
|
+
data << (padlen.chr * padlen)
|
654
|
+
end
|
475
655
|
|
476
|
-
|
477
|
-
|
656
|
+
aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").encrypt
|
657
|
+
aes.iv = @iv
|
658
|
+
aes.key = @key
|
659
|
+
aes.padding = 0
|
478
660
|
|
479
|
-
|
480
|
-
|
661
|
+
@iv + aes.update(data) + aes.final
|
662
|
+
end
|
481
663
|
|
482
|
-
|
664
|
+
def decrypt(data)
|
665
|
+
unless data.size % BLOCKSIZE == 0
|
666
|
+
raise EncryptionError, "Data must be 16-bytes padded (data size = #{data.size} bytes)"
|
667
|
+
end
|
483
668
|
|
484
|
-
|
485
|
-
# Get the stream encryption cipher.
|
486
|
-
# The cipher used may depend on the presence of a Crypt filter.
|
487
|
-
#
|
488
|
-
def get_encryption_cipher
|
489
|
-
if self.filters.first == :Crypt
|
490
|
-
params = decode_params.first
|
669
|
+
@iv = data.slice!(0, BLOCKSIZE)
|
491
670
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
end
|
671
|
+
aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").decrypt
|
672
|
+
aes.iv = @iv
|
673
|
+
aes.key = @key
|
674
|
+
aes.padding = 0
|
497
675
|
|
498
|
-
|
499
|
-
else
|
500
|
-
cipher = self.document.stream_encryption_cipher
|
501
|
-
end
|
676
|
+
plain = (aes.update(data) + aes.final).unpack("C*")
|
502
677
|
|
503
|
-
|
678
|
+
if @use_padding
|
679
|
+
padlen = plain[-1]
|
680
|
+
unless padlen.between?(1, 16)
|
681
|
+
raise EncryptionError, "Incorrect padding length : #{padlen}"
|
682
|
+
end
|
504
683
|
|
505
|
-
|
506
|
-
|
684
|
+
padlen.times do
|
685
|
+
pad = plain.pop
|
686
|
+
raise EncryptionError, "Incorrect padding byte : 0x#{pad.to_s 16}" if pad != padlen
|
687
|
+
end
|
507
688
|
end
|
508
689
|
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
module Identity
|
513
|
-
def Identity.encrypt(_key, data)
|
514
|
-
data
|
515
|
-
end
|
690
|
+
plain.pack("C*")
|
691
|
+
end
|
692
|
+
end
|
516
693
|
|
517
|
-
|
518
|
-
|
519
|
-
|
694
|
+
#
|
695
|
+
# Class representing a crypt filter Dictionary
|
696
|
+
#
|
697
|
+
class CryptFilterDictionary < Dictionary
|
698
|
+
include StandardObject
|
699
|
+
|
700
|
+
field :Type, Type: Name, Default: :CryptFilter
|
701
|
+
field :CFM, Type: Name, Default: :None
|
702
|
+
field :AuthEvent, Type: Name, Default: :DocOpen
|
703
|
+
field :Length, Type: Integer
|
704
|
+
end
|
705
|
+
|
706
|
+
#
|
707
|
+
# Common class for encryption dictionaries.
|
708
|
+
#
|
709
|
+
class EncryptionDictionary < Dictionary
|
710
|
+
include StandardObject
|
711
|
+
|
712
|
+
field :Filter, Type: Name, Default: :Standard, Required: true
|
713
|
+
field :SubFilter, Type: Name, Version: "1.3"
|
714
|
+
field :V, Type: Integer, Default: 0
|
715
|
+
field :Length, Type: Integer, Default: 40, Version: "1.4"
|
716
|
+
field :CF, Type: Dictionary, Version: "1.5"
|
717
|
+
field :StmF, Type: Name, Default: :Identity, Version: "1.5"
|
718
|
+
field :StrF, Type: Name, Default: :Identity, Version: "1.5"
|
719
|
+
field :EFF, Type: Name, Version: "1.6"
|
720
|
+
|
721
|
+
#
|
722
|
+
# Returns the default string encryption cipher.
|
723
|
+
#
|
724
|
+
def string_encryption_cipher
|
725
|
+
encryption_cipher(self.StrF || :Identity)
|
726
|
+
end
|
727
|
+
|
728
|
+
#
|
729
|
+
# Returns the default stream encryption cipher.
|
730
|
+
#
|
731
|
+
def stream_encryption_cipher
|
732
|
+
encryption_cipher(self.StmF || :Identity)
|
733
|
+
end
|
734
|
+
|
735
|
+
#
|
736
|
+
# Returns the encryption cipher corresponding to a crypt filter name.
|
737
|
+
#
|
738
|
+
def encryption_cipher(name)
|
739
|
+
case self.V.to_i
|
740
|
+
when 1, 2
|
741
|
+
Encryption::RC4
|
742
|
+
when 4, 5
|
743
|
+
return Encryption::Identity if name == :Identity
|
744
|
+
|
745
|
+
select_cipher_by_name(name)
|
746
|
+
else
|
747
|
+
raise EncryptionNotSupportedError, "Unsupported encryption version: #{handler.V}"
|
748
|
+
end
|
749
|
+
end
|
750
|
+
|
751
|
+
private
|
752
|
+
|
753
|
+
#
|
754
|
+
# Returns the cipher associated with a crypt filter name.
|
755
|
+
#
|
756
|
+
def select_cipher_by_name(name)
|
757
|
+
raise EncryptionError, "Broken CF entry" unless self.CF.is_a?(Dictionary)
|
758
|
+
|
759
|
+
self.CF.select { |key, dict| key == name and dict.is_a?(Dictionary) }
|
760
|
+
.map { |_, dict| cipher_from_crypt_filter_method(dict[:CFM] || :None) }
|
761
|
+
.first
|
762
|
+
end
|
763
|
+
|
764
|
+
#
|
765
|
+
# Converts a crypt filter method identifier to its cipher class.
|
766
|
+
#
|
767
|
+
def cipher_from_crypt_filter_method(name)
|
768
|
+
case name.to_sym
|
769
|
+
when :None then Encryption::Identity
|
770
|
+
when :V2 then Encryption::RC4
|
771
|
+
when :AESV2 then Encryption::AES
|
772
|
+
when :AESV3
|
773
|
+
raise EncryptionNotSupportedError, "AESV3 requires a version 5 handler" if self.V.to_i != 5
|
774
|
+
Encryption::AES
|
775
|
+
else
|
776
|
+
raise EncryptionNotSupportedError, "Unsupported crypt filter method: #{name}"
|
777
|
+
end
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
#
|
782
|
+
# The standard security handler for PDF encryption.
|
783
|
+
#
|
784
|
+
module Standard
|
785
|
+
PADDING = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A".b # :nodoc:
|
786
|
+
|
787
|
+
#
|
788
|
+
# Permission constants for encrypted documents.
|
789
|
+
#
|
790
|
+
module Permissions
|
791
|
+
RESERVED = 1 << 6 | 1 << 7 | 0xFFFFF000
|
792
|
+
PRINT = 1 << 2 | RESERVED
|
793
|
+
MODIFY_CONTENTS = 1 << 3 | RESERVED
|
794
|
+
COPY_CONTENTS = 1 << 4 | RESERVED
|
795
|
+
MODIFY_ANNOTATIONS = 1 << 5 | RESERVED
|
796
|
+
FILLIN_FORMS = 1 << 8 | RESERVED
|
797
|
+
EXTRACT_CONTENTS = 1 << 9 | RESERVED
|
798
|
+
ASSEMBLE_DOC = 1 << 10 | RESERVED
|
799
|
+
HIGH_QUALITY_PRINT = 1 << 11 | RESERVED
|
800
|
+
|
801
|
+
ALL = PRINT | MODIFY_CONTENTS | COPY_CONTENTS |
|
802
|
+
MODIFY_ANNOTATIONS | FILLIN_FORMS | EXTRACT_CONTENTS |
|
803
|
+
ASSEMBLE_DOC | HIGH_QUALITY_PRINT
|
804
|
+
end
|
805
|
+
|
806
|
+
#
|
807
|
+
# Class defining a standard encryption dictionary.
|
808
|
+
#
|
809
|
+
class Dictionary < EncryptionDictionary
|
810
|
+
field :R, Type: Number, Required: true
|
811
|
+
field :O, Type: String, Required: true
|
812
|
+
field :U, Type: String, Required: true
|
813
|
+
field :OE, Type: String, Version: '1.7', ExtensionLevel: 3
|
814
|
+
field :UE, Type: String, Version: '1.7', ExtensionLevel: 3
|
815
|
+
field :Perms, Type: String, Version: '1.7', ExtensionLevel: 3
|
816
|
+
field :P, Type: Integer, Default: 0, Required: true
|
817
|
+
field :EncryptMetadata, Type: Boolean, Default: true, Version: "1.5"
|
818
|
+
|
819
|
+
def version_required # :nodoc:
|
820
|
+
if self.R > 5
|
821
|
+
['1.7', 8]
|
822
|
+
else
|
823
|
+
super
|
824
|
+
end
|
520
825
|
end
|
521
826
|
|
522
827
|
#
|
523
|
-
#
|
828
|
+
# Checks the given password and derives the document encryption key.
|
829
|
+
# Raises EncryptionInvalidPasswordError on invalid password.
|
524
830
|
#
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
#
|
535
|
-
# Decrypts data using the given key
|
536
|
-
#
|
537
|
-
def RC4.decrypt(key, data)
|
538
|
-
RC4.new(key).decrypt(data)
|
831
|
+
def derive_encryption_key(passwd, doc_id)
|
832
|
+
if is_user_password?(passwd, doc_id)
|
833
|
+
compute_user_encryption_key(passwd, doc_id)
|
834
|
+
elsif is_owner_password?(passwd, doc_id)
|
835
|
+
if self.V.to_i < 5
|
836
|
+
user_passwd = retrieve_user_password(passwd)
|
837
|
+
compute_user_encryption_key(user_passwd, doc_id)
|
838
|
+
else
|
839
|
+
compute_owner_encryption_key(passwd)
|
539
840
|
end
|
841
|
+
else
|
842
|
+
raise EncryptionInvalidPasswordError
|
843
|
+
end
|
844
|
+
end
|
540
845
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
846
|
+
#
|
847
|
+
# Computes the key that will be used to encrypt/decrypt the document contents with user password.
|
848
|
+
# Called at all revisions.
|
849
|
+
#
|
850
|
+
def compute_user_encryption_key(user_password, file_id)
|
851
|
+
return compute_legacy_user_encryption_key(user_password, file_id) if self.R < 5
|
547
852
|
|
548
|
-
|
549
|
-
# Encrypt/decrypt data with the RC4 encryption algorithm
|
550
|
-
#
|
551
|
-
def cipher(data)
|
552
|
-
return '' if data.empty?
|
853
|
+
passwd = password_to_utf8(user_password)
|
553
854
|
|
554
|
-
|
555
|
-
rc4.key_len = @key.length
|
556
|
-
rc4.key = @key
|
855
|
+
uks = self.U[40, 8]
|
557
856
|
|
558
|
-
|
559
|
-
|
857
|
+
ukey = if self.R == 5
|
858
|
+
Digest::SHA256.digest(passwd + uks)
|
859
|
+
else
|
860
|
+
compute_hardened_hash(passwd, uks)
|
861
|
+
end
|
560
862
|
|
561
|
-
|
562
|
-
|
863
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
864
|
+
AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
|
563
865
|
end
|
564
866
|
|
565
867
|
#
|
566
|
-
#
|
868
|
+
# Computes the key that will be used to encrypt/decrypt the document contents.
|
869
|
+
# Only for Revision 4 and less.
|
567
870
|
#
|
568
|
-
|
569
|
-
|
871
|
+
def compute_legacy_user_encryption_key(user_password, file_id)
|
872
|
+
padded = pad_password(user_password)
|
873
|
+
padded.force_encoding('binary')
|
570
874
|
|
571
|
-
|
875
|
+
padded << self.O
|
876
|
+
padded << [self.P].pack("i")
|
572
877
|
|
573
|
-
|
574
|
-
AES.new(key, iv).encrypt(data)
|
575
|
-
end
|
878
|
+
padded << file_id
|
576
879
|
|
577
|
-
|
578
|
-
|
579
|
-
end
|
880
|
+
encrypt_metadata = self.EncryptMetadata != false
|
881
|
+
padded << [-1].pack("i") if (self.R >= 4) && !encrypt_metadata
|
580
882
|
|
581
|
-
|
582
|
-
unless [16, 24, 32].include?(key.size)
|
583
|
-
raise EncryptionError, "Key must have a length of 128, 192 or 256 bits."
|
584
|
-
end
|
883
|
+
key = Digest::MD5.digest(padded)
|
585
884
|
|
586
|
-
|
587
|
-
raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
|
588
|
-
end
|
885
|
+
50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
|
589
886
|
|
590
|
-
|
591
|
-
|
592
|
-
@use_padding = use_padding
|
593
|
-
end
|
887
|
+
truncate_key(key)
|
888
|
+
end
|
594
889
|
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
890
|
+
#
|
891
|
+
# Computes the key that will be used to encrypt/decrypt the document contents with owner password.
|
892
|
+
# Revision 5 and above.
|
893
|
+
#
|
894
|
+
def compute_owner_encryption_key(owner_password)
|
895
|
+
return if self.R < 5
|
599
896
|
|
600
|
-
|
601
|
-
|
602
|
-
data << (padlen.chr * padlen)
|
603
|
-
end
|
897
|
+
passwd = password_to_utf8(owner_password)
|
898
|
+
oks = self.O[40, 8]
|
604
899
|
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
900
|
+
okey = if self.R == 5
|
901
|
+
Digest::SHA256.digest(passwd + oks + self.U)
|
902
|
+
else
|
903
|
+
compute_hardened_hash(passwd, oks, self.U)
|
904
|
+
end
|
609
905
|
|
610
|
-
|
611
|
-
|
906
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
907
|
+
AES.new(okey, nil, false).decrypt(iv + self.OE.value)
|
908
|
+
end
|
612
909
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
910
|
+
#
|
911
|
+
# Set up document passwords.
|
912
|
+
#
|
913
|
+
def set_passwords(owner_password, user_password, salt = nil)
|
914
|
+
return set_legacy_passwords(owner_password, user_password, salt) if self.R < 5
|
915
|
+
|
916
|
+
upass = password_to_utf8(user_password)
|
917
|
+
opass = password_to_utf8(owner_password)
|
918
|
+
|
919
|
+
uvs, uks, ovs, oks = ::Array.new(4) { Encryption.rand_bytes(8) }
|
920
|
+
file_key = Encryption.strong_rand_bytes(32)
|
921
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
922
|
+
|
923
|
+
if self.R == 5
|
924
|
+
self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
|
925
|
+
self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
|
926
|
+
ukey = Digest::SHA256.digest(upass + uks)
|
927
|
+
okey = Digest::SHA256.digest(opass + oks + self.U)
|
928
|
+
else
|
929
|
+
self.U = compute_hardened_hash(upass, uvs) + uvs + uks
|
930
|
+
self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
|
931
|
+
ukey = compute_hardened_hash(upass, uks)
|
932
|
+
okey = compute_hardened_hash(opass, oks, self.U)
|
933
|
+
end
|
934
|
+
|
935
|
+
self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
|
936
|
+
self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
|
937
|
+
|
938
|
+
perms =
|
939
|
+
[self.P].pack("V") + # 0-3
|
940
|
+
[-1].pack("V") + # 4-7
|
941
|
+
((self.EncryptMetadata == true) ? "T" : "F") + # 8
|
942
|
+
"adb" + # 9-11
|
943
|
+
[0].pack("V") # 12-15
|
944
|
+
|
945
|
+
self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
|
946
|
+
|
947
|
+
file_key
|
948
|
+
end
|
949
|
+
|
950
|
+
#
|
951
|
+
# Set up document passwords.
|
952
|
+
# Only for Revision 4 and less.
|
953
|
+
#
|
954
|
+
def set_legacy_passwords(owner_password, user_password, salt)
|
955
|
+
owner_key = compute_owner_key(owner_password)
|
956
|
+
upadded = pad_password(user_password)
|
617
957
|
|
618
|
-
|
958
|
+
owner_key_hash = RC4.encrypt(owner_key, upadded)
|
959
|
+
19.times { |i| owner_key_hash = RC4.encrypt(xor(owner_key, i + 1), owner_key_hash) } if self.R >= 3
|
619
960
|
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
961
|
+
self.O = owner_key_hash
|
962
|
+
self.U = compute_user_password_hash(user_password, salt)
|
963
|
+
end
|
964
|
+
|
965
|
+
#
|
966
|
+
# Checks user password.
|
967
|
+
# For version 2, 3 and 4, _salt_ is the document ID.
|
968
|
+
# For version 5 and 6, _salt_ is the User Key Salt.
|
969
|
+
#
|
970
|
+
def is_user_password?(pass, salt)
|
971
|
+
if self.R == 2
|
972
|
+
compute_user_password_hash(pass, salt) == self.U
|
973
|
+
elsif (self.R == 3) || (self.R == 4)
|
974
|
+
compute_user_password_hash(pass, salt)[0, 16] == self.U[0, 16]
|
975
|
+
elsif self.R == 5
|
976
|
+
uvs = self.U[32, 8]
|
977
|
+
Digest::SHA256.digest(password_to_utf8(pass) + uvs) == self.U[0, 32]
|
978
|
+
elsif self.R == 6
|
979
|
+
uvs = self.U[32, 8]
|
980
|
+
compute_hardened_hash(password_to_utf8(pass), uvs) == self.U[0, 32]
|
981
|
+
end
|
982
|
+
end
|
624
983
|
|
625
|
-
|
984
|
+
#
|
985
|
+
# Checks owner password.
|
986
|
+
# For version 2,3 and 4, _salt_ is the document ID.
|
987
|
+
# For version 5, _salt_ is (Owner Key Salt + U)
|
988
|
+
#
|
989
|
+
def is_owner_password?(pass, salt)
|
990
|
+
if self.R < 5
|
991
|
+
user_password = retrieve_user_password(pass)
|
992
|
+
is_user_password?(user_password, salt)
|
993
|
+
elsif self.R == 5
|
994
|
+
ovs = self.O[32, 8]
|
995
|
+
Digest::SHA256.digest(password_to_utf8(pass) + ovs + self.U) == self.O[0, 32]
|
996
|
+
elsif self.R == 6
|
997
|
+
ovs = self.O[32, 8]
|
998
|
+
compute_hardened_hash(password_to_utf8(pass), ovs, self.U[0, 48]) == self.O[0, 32]
|
999
|
+
end
|
1000
|
+
end
|
626
1001
|
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
1002
|
+
#
|
1003
|
+
# Retrieve user password from owner password.
|
1004
|
+
# Cannot be used with revision 5.
|
1005
|
+
#
|
1006
|
+
def retrieve_user_password(owner_password)
|
1007
|
+
key = compute_owner_key(owner_password)
|
632
1008
|
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
1009
|
+
if self.R == 2
|
1010
|
+
RC4.decrypt(key, self.O)
|
1011
|
+
elsif (self.R == 3) || (self.R == 4)
|
1012
|
+
user_password = RC4.decrypt(xor(key, 19), self.O)
|
1013
|
+
19.times { |i| user_password = RC4.decrypt(xor(key, 18 - i), user_password) }
|
638
1014
|
|
639
|
-
|
640
|
-
|
1015
|
+
user_password
|
1016
|
+
end
|
641
1017
|
end
|
642
1018
|
|
1019
|
+
private
|
1020
|
+
|
643
1021
|
#
|
644
|
-
#
|
1022
|
+
# Used to encrypt/decrypt the O field.
|
1023
|
+
# Rev 2,3,4: O = crypt(user_pass, owner_key).
|
1024
|
+
# Rev 5: unused.
|
645
1025
|
#
|
646
|
-
|
647
|
-
|
1026
|
+
def compute_owner_key(owner_password) # :nodoc:
|
1027
|
+
opadded = pad_password(owner_password)
|
648
1028
|
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
1029
|
+
owner_key = Digest::MD5.digest(opadded)
|
1030
|
+
50.times { owner_key = Digest::MD5.digest(owner_key) } if self.R >= 3
|
1031
|
+
|
1032
|
+
truncate_key(owner_key)
|
653
1033
|
end
|
654
1034
|
|
655
1035
|
#
|
656
|
-
#
|
1036
|
+
# Compute the value of the U field.
|
1037
|
+
# Cannot be used with revision 5.
|
657
1038
|
#
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
field :Length, :Type => Integer, :Default => 40, :Version => "1.4"
|
665
|
-
field :CF, :Type => Dictionary, :Version => "1.5"
|
666
|
-
field :StmF, :Type => Name, :Default => :Identity, :Version => "1.5"
|
667
|
-
field :StrF, :Type => Name, :Default => :Identity, :Version => "1.5"
|
668
|
-
field :EFF, :Type => Name, :Version => "1.6"
|
669
|
-
|
670
|
-
#
|
671
|
-
# Returns the default string encryption cipher.
|
672
|
-
#
|
673
|
-
def string_encryption_cipher
|
674
|
-
encryption_cipher(self.StrF || :Identity)
|
675
|
-
end
|
1039
|
+
def compute_user_password_hash(user_password, salt) # :nodoc:
|
1040
|
+
if self.R == 2
|
1041
|
+
key = compute_user_encryption_key(user_password, salt)
|
1042
|
+
user_key = RC4.encrypt(key, PADDING)
|
1043
|
+
elsif (self.R == 3) || (self.R == 4)
|
1044
|
+
key = compute_user_encryption_key(user_password, salt)
|
676
1045
|
|
677
|
-
|
678
|
-
|
679
|
-
#
|
680
|
-
def stream_encryption_cipher
|
681
|
-
encryption_cipher(self.StmF || :Identity)
|
682
|
-
end
|
1046
|
+
upadded = PADDING + salt
|
1047
|
+
hash = Digest::MD5.digest(upadded)
|
683
1048
|
|
684
|
-
|
685
|
-
# Returns the encryption cipher corresponding to a crypt filter name.
|
686
|
-
#
|
687
|
-
def encryption_cipher(name)
|
688
|
-
case self.V.to_i
|
689
|
-
when 1, 2
|
690
|
-
Encryption::RC4
|
691
|
-
when 4, 5
|
692
|
-
return Encryption::Identity if name == :Identity
|
693
|
-
|
694
|
-
select_cipher_by_name(name)
|
695
|
-
else
|
696
|
-
raise EncryptionNotSupportedError, "Unsupported encryption version: #{handler.V}"
|
697
|
-
end
|
698
|
-
end
|
1049
|
+
user_key = RC4.encrypt(key, hash)
|
699
1050
|
|
700
|
-
|
1051
|
+
19.times { |i| user_key = RC4.encrypt(xor(key, i + 1), user_key) }
|
701
1052
|
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
def select_cipher_by_name(name)
|
706
|
-
raise EncryptionError, "Broken CF entry" unless self.CF.is_a?(Dictionary)
|
1053
|
+
user_key.ljust(32, 0xFF.chr)
|
1054
|
+
end
|
1055
|
+
end
|
707
1056
|
|
708
|
-
|
709
|
-
|
710
|
-
|
1057
|
+
#
|
1058
|
+
# Computes hardened hash used in revision 6 (extension level 8).
|
1059
|
+
#
|
1060
|
+
def compute_hardened_hash(password, salt, vector = '')
|
1061
|
+
block_size = 32
|
1062
|
+
input = Digest::SHA256.digest(password + salt + vector) + "\x00" * 32
|
1063
|
+
key = input[0, 16]
|
1064
|
+
iv = input[16, 16]
|
1065
|
+
digest, aes, h, x = nil, nil, nil, nil
|
1066
|
+
|
1067
|
+
i = 0
|
1068
|
+
while (i < 64) || (i < x[-1].ord + 32)
|
1069
|
+
|
1070
|
+
block = input[0, block_size]
|
1071
|
+
|
1072
|
+
aes = OpenSSL::Cipher.new("aes-128-cbc").encrypt
|
1073
|
+
aes.iv = iv
|
1074
|
+
aes.key = key
|
1075
|
+
aes.padding = 0
|
1076
|
+
|
1077
|
+
64.times do |j|
|
1078
|
+
x = ''
|
1079
|
+
x += aes.update(password) unless password.empty?
|
1080
|
+
x += aes.update(block)
|
1081
|
+
x += aes.update(vector) unless vector.empty?
|
1082
|
+
|
1083
|
+
if j == 0
|
1084
|
+
block_size = 32 + (x.unpack("C16").inject(0) { |a, b| a + b } % 3) * 16
|
1085
|
+
digest = Digest::SHA2.new(block_size << 3)
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
digest.update(x)
|
711
1089
|
end
|
712
1090
|
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
raise EncryptionNotSupportedError, "AESV3 requires a version 5 handler" if self.V.to_i != 5
|
723
|
-
Encryption::AES
|
724
|
-
else
|
725
|
-
raise EncryptionNotSupportedError, "Unsupported crypt filter method: #{name}"
|
726
|
-
end
|
727
|
-
end
|
1091
|
+
h = digest.digest
|
1092
|
+
key = h[0, 16]
|
1093
|
+
input[0, block_size] = h[0, block_size]
|
1094
|
+
iv = h[16, 16]
|
1095
|
+
|
1096
|
+
i += 1
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
h[0, 32]
|
728
1100
|
end
|
729
1101
|
|
730
1102
|
#
|
731
|
-
#
|
1103
|
+
# Some revision handlers require different key sizes.
|
1104
|
+
# Revision 2 uses 40-bit keys.
|
1105
|
+
# Revisions 3 and higher rely on the Length field for the key size.
|
732
1106
|
#
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
RESERVED = 1 << 6 | 1 << 7 | 0xFFFFF000
|
741
|
-
PRINT = 1 << 2 | RESERVED
|
742
|
-
MODIFY_CONTENTS = 1 << 3 | RESERVED
|
743
|
-
COPY_CONTENTS = 1 << 4 | RESERVED
|
744
|
-
MODIFY_ANNOTATIONS = 1 << 5 | RESERVED
|
745
|
-
FILLIN_FORMS = 1 << 8 | RESERVED
|
746
|
-
EXTRACT_CONTENTS = 1 << 9 | RESERVED
|
747
|
-
ASSEMBLE_DOC = 1 << 10 | RESERVED
|
748
|
-
HIGH_QUALITY_PRINT = 1 << 11 | RESERVED
|
749
|
-
|
750
|
-
ALL = PRINT | MODIFY_CONTENTS | COPY_CONTENTS |
|
751
|
-
MODIFY_ANNOTATIONS | FILLIN_FORMS | EXTRACT_CONTENTS |
|
752
|
-
ASSEMBLE_DOC | HIGH_QUALITY_PRINT
|
753
|
-
end
|
1107
|
+
def truncate_key(key)
|
1108
|
+
if self.R == 2
|
1109
|
+
key[0, 5]
|
1110
|
+
elsif self.R >= 3
|
1111
|
+
key[0, self.Length / 8]
|
1112
|
+
end
|
1113
|
+
end
|
754
1114
|
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
field :O, :Type => String, :Required => true
|
762
|
-
field :U, :Type => String, :Required => true
|
763
|
-
field :OE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
764
|
-
field :UE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
765
|
-
field :Perms, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
766
|
-
field :P, :Type => Integer, :Default => 0, :Required => true
|
767
|
-
field :EncryptMetadata, :Type => Boolean, :Default => true, :Version => "1.5"
|
768
|
-
|
769
|
-
def version_required #:nodoc:
|
770
|
-
if self.R > 5
|
771
|
-
[ '1.7', 8 ]
|
772
|
-
else
|
773
|
-
super
|
774
|
-
end
|
775
|
-
end
|
776
|
-
|
777
|
-
#
|
778
|
-
# Checks the given password and derives the document encryption key.
|
779
|
-
# Raises EncryptionInvalidPasswordError on invalid password.
|
780
|
-
#
|
781
|
-
def derive_encryption_key(passwd, doc_id)
|
782
|
-
if is_user_password?(passwd, doc_id)
|
783
|
-
compute_user_encryption_key(passwd, doc_id)
|
784
|
-
elsif is_owner_password?(passwd, doc_id)
|
785
|
-
if self.V.to_i < 5
|
786
|
-
user_passwd = retrieve_user_password(passwd)
|
787
|
-
compute_user_encryption_key(user_passwd, doc_id)
|
788
|
-
else
|
789
|
-
compute_owner_encryption_key(passwd)
|
790
|
-
end
|
791
|
-
else
|
792
|
-
raise EncryptionInvalidPasswordError
|
793
|
-
end
|
794
|
-
end
|
795
|
-
|
796
|
-
#
|
797
|
-
# Computes the key that will be used to encrypt/decrypt the document contents with user password.
|
798
|
-
# Called at all revisions.
|
799
|
-
#
|
800
|
-
def compute_user_encryption_key(user_password, file_id)
|
801
|
-
return compute_legacy_user_encryption_key(user_password, file_id) if self.R < 5
|
802
|
-
|
803
|
-
passwd = password_to_utf8(user_password)
|
804
|
-
|
805
|
-
uks = self.U[40, 8]
|
806
|
-
|
807
|
-
if self.R == 5
|
808
|
-
ukey = Digest::SHA256.digest(passwd + uks)
|
809
|
-
else
|
810
|
-
ukey = compute_hardened_hash(passwd, uks)
|
811
|
-
end
|
812
|
-
|
813
|
-
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
814
|
-
AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
|
815
|
-
end
|
816
|
-
|
817
|
-
#
|
818
|
-
# Computes the key that will be used to encrypt/decrypt the document contents.
|
819
|
-
# Only for Revision 4 and less.
|
820
|
-
#
|
821
|
-
def compute_legacy_user_encryption_key(user_password, file_id)
|
822
|
-
padded = pad_password(user_password)
|
823
|
-
padded.force_encoding('binary')
|
824
|
-
|
825
|
-
padded << self.O
|
826
|
-
padded << [ self.P ].pack("i")
|
827
|
-
|
828
|
-
padded << file_id
|
829
|
-
|
830
|
-
encrypt_metadata = self.EncryptMetadata != false
|
831
|
-
padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata
|
832
|
-
|
833
|
-
key = Digest::MD5.digest(padded)
|
834
|
-
|
835
|
-
50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
|
836
|
-
|
837
|
-
truncate_key(key)
|
838
|
-
end
|
839
|
-
|
840
|
-
#
|
841
|
-
# Computes the key that will be used to encrypt/decrypt the document contents with owner password.
|
842
|
-
# Revision 5 and above.
|
843
|
-
#
|
844
|
-
def compute_owner_encryption_key(owner_password)
|
845
|
-
return if self.R < 5
|
846
|
-
|
847
|
-
passwd = password_to_utf8(owner_password)
|
848
|
-
oks = self.O[40, 8]
|
849
|
-
|
850
|
-
if self.R == 5
|
851
|
-
okey = Digest::SHA256.digest(passwd + oks + self.U)
|
852
|
-
else
|
853
|
-
okey = compute_hardened_hash(passwd, oks, self.U)
|
854
|
-
end
|
855
|
-
|
856
|
-
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
857
|
-
AES.new(okey, nil, false).decrypt(iv + self.OE.value)
|
858
|
-
end
|
859
|
-
|
860
|
-
#
|
861
|
-
# Set up document passwords.
|
862
|
-
#
|
863
|
-
def set_passwords(owner_password, user_password, salt = nil)
|
864
|
-
return set_legacy_passwords(owner_password, user_password, salt) if self.R < 5
|
865
|
-
|
866
|
-
upass = password_to_utf8(user_password)
|
867
|
-
opass = password_to_utf8(owner_password)
|
868
|
-
|
869
|
-
uvs, uks, ovs, oks = ::Array.new(4) { Encryption.rand_bytes(8) }
|
870
|
-
file_key = Encryption.strong_rand_bytes(32)
|
871
|
-
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
872
|
-
|
873
|
-
if self.R == 5
|
874
|
-
self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
|
875
|
-
self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
|
876
|
-
ukey = Digest::SHA256.digest(upass + uks)
|
877
|
-
okey = Digest::SHA256.digest(opass + oks + self.U)
|
878
|
-
else
|
879
|
-
self.U = compute_hardened_hash(upass, uvs) + uvs + uks
|
880
|
-
self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
|
881
|
-
ukey = compute_hardened_hash(upass, uks)
|
882
|
-
okey = compute_hardened_hash(opass, oks, self.U)
|
883
|
-
end
|
884
|
-
|
885
|
-
self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
|
886
|
-
self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
|
887
|
-
|
888
|
-
perms =
|
889
|
-
[ self.P ].pack("V") + # 0-3
|
890
|
-
[ -1 ].pack("V") + # 4-7
|
891
|
-
(self.EncryptMetadata == true ? "T" : "F") + # 8
|
892
|
-
"adb" + # 9-11
|
893
|
-
[ 0 ].pack("V") # 12-15
|
894
|
-
|
895
|
-
self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
|
896
|
-
|
897
|
-
file_key
|
898
|
-
end
|
899
|
-
|
900
|
-
#
|
901
|
-
# Set up document passwords.
|
902
|
-
# Only for Revision 4 and less.
|
903
|
-
#
|
904
|
-
def set_legacy_passwords(owner_password, user_password, salt)
|
905
|
-
owner_key = compute_owner_key(owner_password)
|
906
|
-
upadded = pad_password(user_password)
|
907
|
-
|
908
|
-
owner_key_hash = RC4.encrypt(owner_key, upadded)
|
909
|
-
19.times { |i| owner_key_hash = RC4.encrypt(xor(owner_key, i + 1), owner_key_hash) } if self.R >= 3
|
910
|
-
|
911
|
-
self.O = owner_key_hash
|
912
|
-
self.U = compute_user_password_hash(user_password, salt)
|
913
|
-
end
|
914
|
-
|
915
|
-
#
|
916
|
-
# Checks user password.
|
917
|
-
# For version 2, 3 and 4, _salt_ is the document ID.
|
918
|
-
# For version 5 and 6, _salt_ is the User Key Salt.
|
919
|
-
#
|
920
|
-
def is_user_password?(pass, salt)
|
921
|
-
|
922
|
-
if self.R == 2
|
923
|
-
compute_user_password_hash(pass, salt) == self.U
|
924
|
-
elsif self.R == 3 or self.R == 4
|
925
|
-
compute_user_password_hash(pass, salt)[0, 16] == self.U[0, 16]
|
926
|
-
elsif self.R == 5
|
927
|
-
uvs = self.U[32, 8]
|
928
|
-
Digest::SHA256.digest(password_to_utf8(pass) + uvs) == self.U[0, 32]
|
929
|
-
elsif self.R == 6
|
930
|
-
uvs = self.U[32, 8]
|
931
|
-
compute_hardened_hash(password_to_utf8(pass), uvs) == self.U[0, 32]
|
932
|
-
end
|
933
|
-
end
|
934
|
-
|
935
|
-
#
|
936
|
-
# Checks owner password.
|
937
|
-
# For version 2,3 and 4, _salt_ is the document ID.
|
938
|
-
# For version 5, _salt_ is (Owner Key Salt + U)
|
939
|
-
#
|
940
|
-
def is_owner_password?(pass, salt)
|
941
|
-
|
942
|
-
if self.R < 5
|
943
|
-
user_password = retrieve_user_password(pass)
|
944
|
-
is_user_password?(user_password, salt)
|
945
|
-
elsif self.R == 5
|
946
|
-
ovs = self.O[32, 8]
|
947
|
-
Digest::SHA256.digest(password_to_utf8(pass) + ovs + self.U) == self.O[0, 32]
|
948
|
-
elsif self.R == 6
|
949
|
-
ovs = self.O[32, 8]
|
950
|
-
compute_hardened_hash(password_to_utf8(pass), ovs, self.U[0,48]) == self.O[0, 32]
|
951
|
-
end
|
952
|
-
end
|
953
|
-
|
954
|
-
#
|
955
|
-
# Retrieve user password from owner password.
|
956
|
-
# Cannot be used with revision 5.
|
957
|
-
#
|
958
|
-
def retrieve_user_password(owner_password)
|
959
|
-
|
960
|
-
key = compute_owner_key(owner_password)
|
961
|
-
|
962
|
-
if self.R == 2
|
963
|
-
RC4.decrypt(key, self.O)
|
964
|
-
elsif self.R == 3 or self.R == 4
|
965
|
-
user_password = RC4.decrypt(xor(key, 19), self.O)
|
966
|
-
19.times { |i| user_password = RC4.decrypt(xor(key, 18-i), user_password) }
|
967
|
-
|
968
|
-
user_password
|
969
|
-
end
|
970
|
-
end
|
971
|
-
|
972
|
-
private
|
973
|
-
|
974
|
-
#
|
975
|
-
# Used to encrypt/decrypt the O field.
|
976
|
-
# Rev 2,3,4: O = crypt(user_pass, owner_key).
|
977
|
-
# Rev 5: unused.
|
978
|
-
#
|
979
|
-
def compute_owner_key(owner_password) #:nodoc:
|
980
|
-
|
981
|
-
opadded = pad_password(owner_password)
|
982
|
-
|
983
|
-
owner_key = Digest::MD5.digest(opadded)
|
984
|
-
50.times { owner_key = Digest::MD5.digest(owner_key) } if self.R >= 3
|
985
|
-
|
986
|
-
truncate_key(owner_key)
|
987
|
-
end
|
988
|
-
|
989
|
-
#
|
990
|
-
# Compute the value of the U field.
|
991
|
-
# Cannot be used with revision 5.
|
992
|
-
#
|
993
|
-
def compute_user_password_hash(user_password, salt) #:nodoc:
|
994
|
-
|
995
|
-
if self.R == 2
|
996
|
-
key = compute_user_encryption_key(user_password, salt)
|
997
|
-
user_key = RC4.encrypt(key, PADDING)
|
998
|
-
elsif self.R == 3 or self.R == 4
|
999
|
-
key = compute_user_encryption_key(user_password, salt)
|
1000
|
-
|
1001
|
-
upadded = PADDING + salt
|
1002
|
-
hash = Digest::MD5.digest(upadded)
|
1003
|
-
|
1004
|
-
user_key = RC4.encrypt(key, hash)
|
1005
|
-
|
1006
|
-
19.times { |i| user_key = RC4.encrypt(xor(key,i+1), user_key) }
|
1007
|
-
|
1008
|
-
user_key.ljust(32, 0xFF.chr)
|
1009
|
-
end
|
1010
|
-
end
|
1011
|
-
|
1012
|
-
#
|
1013
|
-
# Computes hardened hash used in revision 6 (extension level 8).
|
1014
|
-
#
|
1015
|
-
def compute_hardened_hash(password, salt, vector = '')
|
1016
|
-
block_size = 32
|
1017
|
-
input = Digest::SHA256.digest(password + salt + vector) + "\x00" * 32
|
1018
|
-
key = input[0, 16]
|
1019
|
-
iv = input[16, 16]
|
1020
|
-
digest, aes, h, x = nil, nil, nil, nil
|
1021
|
-
|
1022
|
-
i = 0
|
1023
|
-
while i < 64 or i < x[-1].ord + 32
|
1024
|
-
|
1025
|
-
block = input[0, block_size]
|
1026
|
-
|
1027
|
-
aes = OpenSSL::Cipher.new("aes-128-cbc").encrypt
|
1028
|
-
aes.iv = iv
|
1029
|
-
aes.key = key
|
1030
|
-
aes.padding = 0
|
1031
|
-
|
1032
|
-
64.times do |j|
|
1033
|
-
x = ''
|
1034
|
-
x += aes.update(password) unless password.empty?
|
1035
|
-
x += aes.update(block)
|
1036
|
-
x += aes.update(vector) unless vector.empty?
|
1037
|
-
|
1038
|
-
if j == 0
|
1039
|
-
block_size = 32 + (x.unpack("C16").inject(0) {|a,b| a+b} % 3) * 16
|
1040
|
-
digest = Digest::SHA2.new(block_size << 3)
|
1041
|
-
end
|
1042
|
-
|
1043
|
-
digest.update(x)
|
1044
|
-
end
|
1045
|
-
|
1046
|
-
h = digest.digest
|
1047
|
-
key = h[0, 16]
|
1048
|
-
input[0, block_size] = h[0, block_size]
|
1049
|
-
iv = h[16, 16]
|
1050
|
-
|
1051
|
-
i = i + 1
|
1052
|
-
end
|
1053
|
-
|
1054
|
-
h[0, 32]
|
1055
|
-
end
|
1056
|
-
|
1057
|
-
#
|
1058
|
-
# Some revision handlers require different key sizes.
|
1059
|
-
# Revision 2 uses 40-bit keys.
|
1060
|
-
# Revisions 3 and higher rely on the Length field for the key size.
|
1061
|
-
#
|
1062
|
-
def truncate_key(key)
|
1063
|
-
if self.R == 2
|
1064
|
-
key[0, 5]
|
1065
|
-
elsif self.R >= 3
|
1066
|
-
key[0, self.Length / 8]
|
1067
|
-
end
|
1068
|
-
end
|
1069
|
-
|
1070
|
-
def xor(str, byte) #:nodoc:
|
1071
|
-
str.bytes.map!{|b| b ^ byte }.pack("C*")
|
1072
|
-
end
|
1073
|
-
|
1074
|
-
def pad_password(password) #:nodoc:
|
1075
|
-
password[0, 32].ljust(32, PADDING)
|
1076
|
-
end
|
1077
|
-
|
1078
|
-
def password_to_utf8(passwd) #:nodoc:
|
1079
|
-
LiteralString.new(passwd).to_utf8[0, 127]
|
1080
|
-
end
|
1081
|
-
end
|
1115
|
+
def xor(str, byte) # :nodoc:
|
1116
|
+
str.bytes.map! { |b| b ^ byte }.pack("C*")
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
def pad_password(password) # :nodoc:
|
1120
|
+
password[0, 32].ljust(32, PADDING)
|
1082
1121
|
end
|
1083
|
-
end
|
1084
1122
|
|
1123
|
+
def password_to_utf8(passwd) # :nodoc:
|
1124
|
+
LiteralString.new(passwd).to_utf8[0, 127]
|
1125
|
+
end
|
1126
|
+
end
|
1127
|
+
end
|
1128
|
+
end
|
1085
1129
|
end
|