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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/examples/attachments/attachment.rb +7 -8
  4. data/examples/attachments/nested_document.rb +6 -5
  5. data/examples/encryption/encryption.rb +5 -4
  6. data/examples/events/events.rb +7 -6
  7. data/examples/flash/flash.rb +10 -9
  8. data/examples/forms/javascript.rb +14 -13
  9. data/examples/forms/xfa.rb +67 -66
  10. data/examples/javascript/hello_world.rb +6 -5
  11. data/examples/javascript/js_emulation.rb +26 -26
  12. data/examples/loop/goto.rb +12 -11
  13. data/examples/loop/named.rb +17 -16
  14. data/examples/signature/signature.rb +11 -11
  15. data/examples/uri/javascript.rb +25 -24
  16. data/examples/uri/open-uri.rb +5 -4
  17. data/examples/uri/submitform.rb +11 -10
  18. data/lib/origami/3d.rb +330 -334
  19. data/lib/origami/acroform.rb +267 -268
  20. data/lib/origami/actions.rb +266 -278
  21. data/lib/origami/annotations.rb +659 -670
  22. data/lib/origami/array.rb +192 -196
  23. data/lib/origami/boolean.rb +66 -70
  24. data/lib/origami/catalog.rb +360 -363
  25. data/lib/origami/collections.rb +132 -133
  26. data/lib/origami/compound.rb +125 -129
  27. data/lib/origami/destinations.rb +226 -237
  28. data/lib/origami/dictionary.rb +155 -154
  29. data/lib/origami/encryption.rb +967 -923
  30. data/lib/origami/extensions/fdf.rb +270 -275
  31. data/lib/origami/extensions/ppklite.rb +323 -328
  32. data/lib/origami/filespec.rb +170 -173
  33. data/lib/origami/filters/ascii.rb +162 -167
  34. data/lib/origami/filters/ccitt/tables.rb +248 -252
  35. data/lib/origami/filters/ccitt.rb +309 -312
  36. data/lib/origami/filters/crypt.rb +31 -34
  37. data/lib/origami/filters/dct.rb +47 -50
  38. data/lib/origami/filters/flate.rb +57 -60
  39. data/lib/origami/filters/jbig2.rb +50 -53
  40. data/lib/origami/filters/jpx.rb +40 -43
  41. data/lib/origami/filters/lzw.rb +151 -155
  42. data/lib/origami/filters/predictors.rb +250 -255
  43. data/lib/origami/filters/runlength.rb +111 -115
  44. data/lib/origami/filters.rb +319 -325
  45. data/lib/origami/font.rb +173 -177
  46. data/lib/origami/functions.rb +62 -66
  47. data/lib/origami/graphics/colors.rb +203 -208
  48. data/lib/origami/graphics/instruction.rb +79 -81
  49. data/lib/origami/graphics/path.rb +141 -144
  50. data/lib/origami/graphics/patterns.rb +156 -160
  51. data/lib/origami/graphics/render.rb +51 -47
  52. data/lib/origami/graphics/state.rb +144 -142
  53. data/lib/origami/graphics/text.rb +185 -188
  54. data/lib/origami/graphics/xobject.rb +818 -804
  55. data/lib/origami/graphics.rb +25 -26
  56. data/lib/origami/header.rb +63 -65
  57. data/lib/origami/javascript.rb +718 -651
  58. data/lib/origami/linearization.rb +284 -285
  59. data/lib/origami/metadata.rb +156 -135
  60. data/lib/origami/name.rb +98 -100
  61. data/lib/origami/null.rb +49 -51
  62. data/lib/origami/numeric.rb +133 -135
  63. data/lib/origami/obfuscation.rb +180 -182
  64. data/lib/origami/object.rb +634 -631
  65. data/lib/origami/optionalcontent.rb +147 -149
  66. data/lib/origami/outline.rb +46 -48
  67. data/lib/origami/outputintents.rb +76 -77
  68. data/lib/origami/page.rb +637 -596
  69. data/lib/origami/parser.rb +214 -221
  70. data/lib/origami/parsers/fdf.rb +44 -45
  71. data/lib/origami/parsers/pdf/lazy.rb +147 -154
  72. data/lib/origami/parsers/pdf/linear.rb +104 -109
  73. data/lib/origami/parsers/pdf.rb +109 -107
  74. data/lib/origami/parsers/ppklite.rb +44 -46
  75. data/lib/origami/pdf.rb +886 -896
  76. data/lib/origami/reference.rb +116 -120
  77. data/lib/origami/signature.rb +617 -625
  78. data/lib/origami/stream.rb +560 -558
  79. data/lib/origami/string.rb +366 -368
  80. data/lib/origami/template/patterns.rb +50 -52
  81. data/lib/origami/template/widgets.rb +111 -114
  82. data/lib/origami/trailer.rb +153 -157
  83. data/lib/origami/tree.rb +55 -57
  84. data/lib/origami/version.rb +19 -19
  85. data/lib/origami/webcapture.rb +87 -90
  86. data/lib/origami/xfa/config.rb +409 -414
  87. data/lib/origami/xfa/connectionset.rb +113 -117
  88. data/lib/origami/xfa/datasets.rb +38 -42
  89. data/lib/origami/xfa/localeset.rb +33 -37
  90. data/lib/origami/xfa/package.rb +49 -52
  91. data/lib/origami/xfa/pdf.rb +54 -59
  92. data/lib/origami/xfa/signature.rb +33 -37
  93. data/lib/origami/xfa/sourceset.rb +34 -38
  94. data/lib/origami/xfa/stylesheet.rb +35 -39
  95. data/lib/origami/xfa/template.rb +1630 -1634
  96. data/lib/origami/xfa/xdc.rb +33 -37
  97. data/lib/origami/xfa/xfa.rb +132 -123
  98. data/lib/origami/xfa/xfdf.rb +34 -38
  99. data/lib/origami/xfa/xmpmeta.rb +34 -38
  100. data/lib/origami/xfa.rb +50 -53
  101. data/lib/origami/xreftable.rb +462 -462
  102. data/lib/origami.rb +37 -38
  103. data/test/test_actions.rb +22 -20
  104. data/test/test_annotations.rb +54 -52
  105. data/test/test_forms.rb +23 -21
  106. data/test/test_native_types.rb +82 -78
  107. data/test/test_object_tree.rb +25 -24
  108. data/test/test_pages.rb +43 -41
  109. data/test/test_pdf.rb +2 -0
  110. data/test/test_pdf_attachment.rb +23 -21
  111. data/test/test_pdf_create.rb +16 -15
  112. data/test/test_pdf_encrypt.rb +69 -66
  113. data/test/test_pdf_parse.rb +131 -129
  114. data/test/test_pdf_parse_lazy.rb +53 -53
  115. data/test/test_pdf_sign.rb +67 -67
  116. data/test/test_streams.rb +145 -143
  117. data/test/test_xrefs.rb +46 -45
  118. metadata +64 -8
@@ -1,22 +1,22 @@
1
- =begin
2
-
3
- This file is part of Origami, PDF manipulation framework for Ruby
4
- Copyright (C) 2016 Guillaume Delugré.
5
-
6
- Origami is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU Lesser General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- Origami is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU Lesser General Public License for more details.
15
-
16
- You should have received a copy of the GNU Lesser General Public License
17
- along with Origami. If not, see <http://www.gnu.org/licenses/>.
18
-
19
- =end
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
- class EncryptionError < Error #:nodoc:
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
- class EncryptionInvalidPasswordError < EncryptionError #:nodoc:
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
- class EncryptionNotSupportedError < EncryptionError #:nodoc:
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
- class PDF
115
+ private
38
116
 
39
- #
40
- # Returns whether the PDF file is encrypted.
41
- #
42
- def encrypted?
43
- trailer_key? :Encrypt
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
- # Turn the encryption dictionary into a standard encryption dictionary.
54
- handler = trailer_key(:Encrypt)
55
- handler = self.cast_object(handler.reference, Encryption::Standard::Dictionary)
148
+ handler.CF[:StdCF] = crypt_filter
149
+ handler.StmF = handler.StrF = :StdCF
150
+ end
56
151
 
57
- unless handler.Filter == :Standard
58
- raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter}'"
59
- end
152
+ user_passwd, owner_passwd = params[:user_passwd], params[:owner_passwd]
60
153
 
61
- doc_id = trailer_key(:ID)
62
- unless doc_id.is_a?(Array)
63
- raise EncryptionError, "Document ID was not found or is invalid" unless handler.V.to_i == 5
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
- encryption_key = handler.derive_encryption_key(passwd, doc_id)
158
+ # Install the encryption dictionary to the document.
159
+ trailer.Encrypt = self << handler
69
160
 
70
- self.extend(Encryption::EncryptedDocument)
71
- self.encryption_handler = handler
72
- self.encryption_key = encryption_key
161
+ [handler, encryption_key]
162
+ end
73
163
 
74
- decrypt_objects
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
- self
77
- end
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
- # Encrypts the current document with the provided passwords.
81
- # The document will be encrypted at writing-on-disk time.
82
- # _userpasswd_:: The user password.
83
- # _ownerpasswd_:: The owner password.
84
- # _options_:: A set of options to configure encryption.
85
- #
86
- def encrypt(options = {})
87
- raise EncryptionError, "PDF is already encrypted" if self.encrypted?
88
-
89
- #
90
- # Default encryption options.
91
- #
92
- params =
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
- private
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
- # Setup keys.
158
- handler.set_passwords(owner_passwd, user_passwd, doc_id)
159
- encryption_key = handler.compute_user_encryption_key(user_passwd, doc_id)
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
- # Install the encryption dictionary to the document.
162
- self.trailer.Encrypt = self << handler
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
- [ handler, encryption_key ]
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
- # Converts the parameters passed to PDF#encrypt.
169
- # Returns [ version, revision, crypt_filters ]
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
- # Compute the required standard security handler version based on the RC4 key size.
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
- if key_size > 40
191
- version = 2
192
- revision = 3
193
- else
194
- version = 1
195
- revision = 2
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
- [ version, revision ]
340
+ return
199
341
  end
200
342
 
201
- #
202
- # Compute the required standard security handler version based on the AES key size.
203
- # _key_size_:: Key size in bits.
204
- # _hardened_:: Use the extension level 8 hardened derivation algorithm.
205
- # Returns [ version, revision ].
206
- #
207
- def crypto_revision_from_aes_key(key_size, hardened)
208
- if key_size == 128
209
- version = revision = 4
210
- elsif key_size == 256
211
- version = 5
212
- if hardened
213
- revision = 6
214
- else
215
- revision = 5
216
- end
217
- else
218
- raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
219
- end
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
- [ version, revision ]
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 to provide support for encrypting and decrypting PDF documents.
383
+ # Module for encrypted String.
227
384
  #
228
- module Encryption
385
+ module EncryptedString
386
+ include EncryptedObject
229
387
 
230
- #
231
- # Generates _n_ random bytes from a fast PRNG.
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
- # Generates _n_ random bytes from a crypto PRNG.
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
- module EncryptedDocument
245
- attr_accessor :encryption_key
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
- # Get the encryption cipher from the crypt filter name.
249
- def encryption_cipher(name)
250
- @encryption_handler.encryption_cipher(name)
251
- end
398
+ key = compute_object_key(cipher)
252
399
 
253
- # Get the default string encryption cipher.
254
- def string_encryption_cipher
255
- @encryption_handler.string_encryption_cipher
256
- end
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
- # Get the default stream encryption cipher.
259
- def stream_encryption_cipher
260
- @encryption_handler.stream_encryption_cipher
261
- end
408
+ @decrypted = false
262
409
 
263
- private
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
- # For each object subject to encryption, convert it to an EncryptedObject and mark it as not encrypted yet.
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
- # Iterates over each encryptable objects in the document.
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
- def physicalize(options = {})
325
- encrypt_objects
419
+ cipher = document.string_encryption_cipher
420
+ raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
326
421
 
327
- super
422
+ key = compute_object_key(cipher)
328
423
 
329
- # remove encrypt dictionary if requested
330
- if options[:decrypt]
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
- self
336
- end
427
+ self
428
+ end
429
+ end
337
430
 
338
- def build_object(object, revision, options)
339
- if object.is_a?(EncryptedObject) and options[:decrypt]
340
- object.pre_build
341
- object.decrypt!
342
- object.decrypted = false # makes it believe no encryption pass is required
343
- object.post_build
431
+ #
432
+ # Module for encrypted Stream.
433
+ #
434
+ module EncryptedStream
435
+ include EncryptedObject
344
436
 
345
- return
346
- end
437
+ def self.extended(obj)
438
+ obj.decrypted = false
439
+ end
347
440
 
348
- super
349
- end
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
- def post_build
359
- encrypt!
446
+ cipher = get_encryption_cipher
447
+ key = compute_object_key(cipher)
360
448
 
361
- super
362
- end
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
- private
457
+ @decrypted = false
365
458
 
366
- def compute_object_key(cipher)
367
- doc = self.document
368
- raise EncryptionError, "Document is not encrypted" unless doc.is_a?(EncryptedDocument)
459
+ @encoded_data.freeze
460
+ freeze
369
461
 
370
- encryption_key = doc.encryption_key
462
+ self
463
+ end
371
464
 
372
- if doc.encryption_handler.V < 5
373
- parent = self.indirect_parent
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
- key_len = [k.length, 16].min
378
- k << "sAlT" if cipher == Encryption::AES
468
+ cipher = get_encryption_cipher
469
+ key = compute_object_key(cipher)
379
470
 
380
- Digest::MD5.digest(k)[0, key_len]
381
- else
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
- # Module for encrypted String.
389
- #
390
- module EncryptedString
391
- include EncryptedObject
474
+ self
475
+ end
392
476
 
393
- def self.extended(obj)
394
- obj.decrypted = false
395
- end
477
+ private
396
478
 
397
- def encrypt!
398
- return self unless @decrypted
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
- cipher = self.document.string_encryption_cipher
401
- raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
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
- key = compute_object_key(cipher)
493
+ cipher = document.encryption_cipher(crypt_filter)
494
+ else
495
+ cipher = document.stream_encryption_cipher
496
+ end
404
497
 
405
- encrypted_data =
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
- @decrypted = false
500
+ cipher
501
+ end
502
+ end
414
503
 
415
- self.replace(encrypted_data)
416
- self.freeze
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
- self
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
- def decrypt!
422
- return self if @decrypted
423
-
424
- cipher = self.document.string_encryption_cipher
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
- key = compute_object_key(cipher)
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
- self.replace(cipher.decrypt(key, self.to_str))
430
- @decrypted = true
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
- self
433
- end
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
- # Module for encrypted Stream.
438
- #
439
- module EncryptedStream
440
- include EncryptedObject
609
+ result
610
+ end
441
611
 
442
- def self.extended(obj)
443
- obj.decrypted = false
444
- end
612
+ alias_method :encrypt, :cipher
613
+ alias_method :decrypt, :cipher
614
+ end
445
615
 
446
- def encrypt!
447
- return self unless @decrypted
616
+ #
617
+ # Class wrapper for AES mode CBC.
618
+ #
619
+ class AES
620
+ BLOCKSIZE = 16
448
621
 
449
- encode!
622
+ attr_writer :iv
450
623
 
451
- cipher = get_encryption_cipher
452
- key = compute_object_key(cipher)
624
+ def self.encrypt(key, iv, data)
625
+ AES.new(key, iv).encrypt(data)
626
+ end
453
627
 
454
- @encoded_data =
455
- if cipher == RC4 or cipher == Identity
456
- cipher.encrypt(key, self.encoded_data)
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
- @decrypted = false
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
- @encoded_data.freeze
465
- self.freeze
637
+ if !iv.nil? && (iv.size != BLOCKSIZE)
638
+ raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
639
+ end
466
640
 
467
- self
468
- end
641
+ @key = key
642
+ @iv = iv
643
+ @use_padding = use_padding
644
+ end
469
645
 
470
- def decrypt!
471
- return self if @decrypted
646
+ def encrypt(data)
647
+ if @iv.nil?
648
+ raise EncryptionError, "No initialization vector has been set."
649
+ end
472
650
 
473
- cipher = get_encryption_cipher
474
- key = compute_object_key(cipher)
651
+ if @use_padding
652
+ padlen = BLOCKSIZE - (data.size % BLOCKSIZE)
653
+ data << (padlen.chr * padlen)
654
+ end
475
655
 
476
- self.encoded_data = cipher.decrypt(key, @encoded_data)
477
- @decrypted = true
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
- self
480
- end
661
+ @iv + aes.update(data) + aes.final
662
+ end
481
663
 
482
- private
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
- if params.is_a?(Dictionary) and params.Name.is_a?(Name)
493
- crypt_filter = params.Name.value
494
- else
495
- crypt_filter = :Identity
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
- cipher = self.document.encryption_cipher(crypt_filter)
499
- else
500
- cipher = self.document.stream_encryption_cipher
501
- end
676
+ plain = (aes.update(data) + aes.final).unpack("C*")
502
677
 
503
- raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
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
- cipher
506
- end
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
- # Identity transformation.
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
- def Identity.decrypt(_key, data)
518
- data
519
- end
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
- # Class wrapper for the RC4 algorithm.
828
+ # Checks the given password and derives the document encryption key.
829
+ # Raises EncryptionInvalidPasswordError on invalid password.
524
830
  #
525
- class RC4
526
-
527
- #
528
- # Encrypts data using the given key
529
- #
530
- def RC4.encrypt(key, data)
531
- RC4.new(key).encrypt(data)
532
- end
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
- # Creates and initialises a new RC4 generator using given key
543
- #
544
- def initialize(key)
545
- @key = key
546
- end
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
- rc4 = OpenSSL::Cipher::RC4.new.encrypt
555
- rc4.key_len = @key.length
556
- rc4.key = @key
855
+ uks = self.U[40, 8]
557
856
 
558
- rc4.update(data) + rc4.final
559
- end
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
- alias encrypt cipher
562
- alias decrypt cipher
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
- # Class wrapper for AES mode CBC.
868
+ # Computes the key that will be used to encrypt/decrypt the document contents.
869
+ # Only for Revision 4 and less.
567
870
  #
568
- class AES
569
- BLOCKSIZE = 16
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
- attr_writer :iv
875
+ padded << self.O
876
+ padded << [self.P].pack("i")
572
877
 
573
- def AES.encrypt(key, iv, data)
574
- AES.new(key, iv).encrypt(data)
575
- end
878
+ padded << file_id
576
879
 
577
- def AES.decrypt(key, data)
578
- AES.new(key, nil).decrypt(data)
579
- end
880
+ encrypt_metadata = self.EncryptMetadata != false
881
+ padded << [-1].pack("i") if (self.R >= 4) && !encrypt_metadata
580
882
 
581
- def initialize(key, iv, use_padding = true)
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
- if not iv.nil? and iv.size != BLOCKSIZE
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
- @key = key
591
- @iv = iv
592
- @use_padding = use_padding
593
- end
887
+ truncate_key(key)
888
+ end
594
889
 
595
- def encrypt(data)
596
- if @iv.nil?
597
- raise EncryptionError, "No initialization vector has been set."
598
- end
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
- if @use_padding
601
- padlen = BLOCKSIZE - (data.size % BLOCKSIZE)
602
- data << (padlen.chr * padlen)
603
- end
897
+ passwd = password_to_utf8(owner_password)
898
+ oks = self.O[40, 8]
604
899
 
605
- aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").encrypt
606
- aes.iv = @iv
607
- aes.key = @key
608
- aes.padding = 0
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
- @iv + aes.update(data) + aes.final
611
- end
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
- def decrypt(data)
614
- unless data.size % BLOCKSIZE == 0
615
- raise EncryptionError, "Data must be 16-bytes padded (data size = #{data.size} bytes)"
616
- end
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
- @iv = data.slice!(0, BLOCKSIZE)
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
- aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").decrypt
621
- aes.iv = @iv
622
- aes.key = @key
623
- aes.padding = 0
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
- plain = (aes.update(data) + aes.final).unpack("C*")
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
- if @use_padding
628
- padlen = plain[-1]
629
- unless padlen.between?(1, 16)
630
- raise EncryptionError, "Incorrect padding length : #{padlen}"
631
- end
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
- padlen.times do
634
- pad = plain.pop
635
- raise EncryptionError, "Incorrect padding byte : 0x#{pad.to_s 16}" if pad != padlen
636
- end
637
- end
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
- plain.pack("C*")
640
- end
1015
+ user_password
1016
+ end
641
1017
  end
642
1018
 
1019
+ private
1020
+
643
1021
  #
644
- # Class representing a crypt filter Dictionary
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
- class CryptFilterDictionary < Dictionary
647
- include StandardObject
1026
+ def compute_owner_key(owner_password) # :nodoc:
1027
+ opadded = pad_password(owner_password)
648
1028
 
649
- field :Type, :Type => Name, :Default => :CryptFilter
650
- field :CFM, :Type => Name, :Default => :None
651
- field :AuthEvent, :Type => Name, :Default => :DocOpen
652
- field :Length, :Type => Integer
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
- # Common class for encryption dictionaries.
1036
+ # Compute the value of the U field.
1037
+ # Cannot be used with revision 5.
657
1038
  #
658
- class EncryptionDictionary < Dictionary
659
- include StandardObject
660
-
661
- field :Filter, :Type => Name, :Default => :Standard, :Required => true
662
- field :SubFilter, :Type => Name, :Version => "1.3"
663
- field :V, :Type => Integer, :Default => 0
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
- # Returns the default stream encryption cipher.
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
- private
1051
+ 19.times { |i| user_key = RC4.encrypt(xor(key, i + 1), user_key) }
701
1052
 
702
- #
703
- # Returns the cipher associated with a crypt filter name.
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
- self.CF.select { |key, dict| key == name and dict.is_a?(Dictionary) }
709
- .map { |_, dict| cipher_from_crypt_filter_method(dict[:CFM] || :None) }
710
- .first
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
- # Converts a crypt filter method identifier to its cipher class.
715
- #
716
- def cipher_from_crypt_filter_method(name)
717
- case name.to_sym
718
- when :None then Encryption::Identity
719
- when :V2 then Encryption::RC4
720
- when :AESV2 then Encryption::AES
721
- when :AESV3
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
- # The standard security handler for PDF encryption.
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
- module Standard
734
- 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:
735
-
736
- #
737
- # Permission constants for encrypted documents.
738
- #
739
- module Permissions
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
- # Class defining a standard encryption dictionary.
757
- #
758
- class Dictionary < EncryptionDictionary
759
-
760
- field :R, :Type => Number, :Required => true
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