origami 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/bin/gui/config.rb +2 -1
  4. data/bin/gui/file.rb +118 -240
  5. data/bin/gui/gtkhex.rb +5 -5
  6. data/bin/gui/hexview.rb +20 -16
  7. data/bin/gui/imgview.rb +1 -1
  8. data/bin/gui/menu.rb +138 -158
  9. data/bin/gui/properties.rb +46 -48
  10. data/bin/gui/signing.rb +183 -214
  11. data/bin/gui/textview.rb +1 -1
  12. data/bin/gui/treeview.rb +13 -7
  13. data/bin/gui/walker.rb +102 -71
  14. data/bin/gui/xrefs.rb +1 -1
  15. data/bin/pdf2ruby +3 -3
  16. data/bin/pdfcop +18 -11
  17. data/bin/pdfextract +14 -5
  18. data/bin/pdfmetadata +3 -3
  19. data/bin/shell/console.rb +8 -8
  20. data/bin/shell/hexdump.rb +4 -4
  21. data/examples/attachments/nested_document.rb +1 -1
  22. data/examples/javascript/hello_world.rb +3 -3
  23. data/lib/origami.rb +0 -1
  24. data/lib/origami/acroform.rb +3 -3
  25. data/lib/origami/array.rb +1 -3
  26. data/lib/origami/boolean.rb +1 -3
  27. data/lib/origami/catalog.rb +3 -9
  28. data/lib/origami/destinations.rb +2 -2
  29. data/lib/origami/dictionary.rb +15 -29
  30. data/lib/origami/encryption.rb +334 -692
  31. data/lib/origami/extensions/fdf.rb +3 -2
  32. data/lib/origami/extensions/ppklite.rb +5 -9
  33. data/lib/origami/filespec.rb +2 -2
  34. data/lib/origami/filters.rb +54 -36
  35. data/lib/origami/filters/ascii.rb +67 -49
  36. data/lib/origami/filters/ccitt.rb +4 -236
  37. data/lib/origami/filters/ccitt/tables.rb +267 -0
  38. data/lib/origami/filters/crypt.rb +1 -1
  39. data/lib/origami/filters/dct.rb +0 -1
  40. data/lib/origami/filters/flate.rb +3 -43
  41. data/lib/origami/filters/lzw.rb +62 -99
  42. data/lib/origami/filters/predictors.rb +135 -105
  43. data/lib/origami/filters/runlength.rb +34 -22
  44. data/lib/origami/graphics.rb +2 -2
  45. data/lib/origami/graphics/colors.rb +89 -63
  46. data/lib/origami/graphics/path.rb +14 -14
  47. data/lib/origami/graphics/patterns.rb +31 -33
  48. data/lib/origami/graphics/render.rb +0 -1
  49. data/lib/origami/graphics/state.rb +9 -9
  50. data/lib/origami/graphics/text.rb +17 -17
  51. data/lib/origami/graphics/xobject.rb +102 -92
  52. data/lib/origami/javascript.rb +91 -68
  53. data/lib/origami/linearization.rb +22 -20
  54. data/lib/origami/metadata.rb +1 -1
  55. data/lib/origami/name.rb +1 -3
  56. data/lib/origami/null.rb +1 -3
  57. data/lib/origami/numeric.rb +3 -13
  58. data/lib/origami/object.rb +100 -72
  59. data/lib/origami/page.rb +24 -28
  60. data/lib/origami/parser.rb +34 -51
  61. data/lib/origami/parsers/fdf.rb +2 -2
  62. data/lib/origami/parsers/pdf.rb +41 -18
  63. data/lib/origami/parsers/pdf/lazy.rb +83 -46
  64. data/lib/origami/parsers/pdf/linear.rb +19 -10
  65. data/lib/origami/parsers/ppklite.rb +1 -1
  66. data/lib/origami/pdf.rb +150 -206
  67. data/lib/origami/reference.rb +4 -6
  68. data/lib/origami/signature.rb +76 -48
  69. data/lib/origami/stream.rb +69 -63
  70. data/lib/origami/string.rb +2 -19
  71. data/lib/origami/trailer.rb +25 -22
  72. data/lib/origami/version.rb +1 -1
  73. data/lib/origami/xfa.rb +6 -4
  74. data/lib/origami/xreftable.rb +29 -29
  75. data/test/test_annotations.rb +16 -38
  76. data/test/test_pdf_attachment.rb +1 -1
  77. data/test/test_pdf_parse.rb +1 -1
  78. data/test/test_xrefs.rb +2 -2
  79. metadata +4 -4
  80. data/lib/origami/export.rb +0 -247
@@ -47,15 +47,15 @@ USAGE
47
47
  OptionParser.new do |opts|
48
48
  opts.banner = BANNER
49
49
 
50
- opts.on("-i", "Extracts document info metadata") do |i|
50
+ opts.on("-i", "--info", "Extracts document info metadata") do
51
51
  options[:doc_info] = true
52
52
  end
53
53
 
54
- opts.on("-x", "Extracts XMP document metadata stream") do |i|
54
+ opts.on("-x", "--xmp", "Extracts XMP document metadata stream") do
55
55
  options[:doc_stream] = true
56
56
  end
57
57
 
58
- opts.on("-n", "Turn off colorized output.") do
58
+ opts.on("-n", "--no-color", "Turn off colorized output.") do
59
59
  options[:disable_colors] = true
60
60
  end
61
61
 
@@ -77,20 +77,20 @@ module Origami
77
77
  class Revision
78
78
  def to_s
79
79
  puts "---------- Body ----------".white.bold
80
- @body.each_value { |obj|
80
+ @body.each_value do |obj|
81
81
  print "#{obj.reference.to_s.rjust(8,' ')}".ljust(10).magenta
82
82
  puts "#{obj.type}".yellow
83
- }
83
+ end
84
84
 
85
85
  puts "---------- Trailer ---------".white.bold
86
86
  if not @trailer.dictionary
87
87
  puts " [x] No trailer found.".blue
88
88
  else
89
- @trailer.dictionary.each_pair { |entry, value|
89
+ @trailer.dictionary.each_pair do |entry, value|
90
90
  print " [*] ".magenta
91
- print "#{entry.to_s}: ".yellow
92
- puts "#{value.to_s}".red
93
- }
91
+ print "#{entry}: ".yellow
92
+ puts "#{value}".red
93
+ end
94
94
 
95
95
  print " [+] ".magenta
96
96
  print "startxref: ".yellow
@@ -111,9 +111,9 @@ module Origami
111
111
  print "Version: ".yellow
112
112
  puts "#{@header.major_version}.#{@header.minor_version}".red
113
113
 
114
- @revisions.each { |revision|
114
+ @revisions.each do |revision|
115
115
  revision.to_s
116
- }
116
+ end
117
117
  puts
118
118
  end
119
119
 
@@ -26,14 +26,14 @@ class String #:nodoc:
26
26
  dump = ""
27
27
  counter = 0
28
28
 
29
- while counter < length
29
+ while counter < self.length
30
30
  offset = sprintf("%010X", counter + delta)
31
31
 
32
- linelen = (counter < length - bytesperline) ? bytesperline : (length - counter)
32
+ linelen = [ self.length - counter, bytesperline ].min
33
33
  bytes = ""
34
34
  linelen.times do |i|
35
- byte = self[counter + i].ord.to_s(16)
36
- byte.insert(0, "0") if byte.size < 2
35
+ byte = self[counter + i].ord.to_s(16).rjust(2, '0')
36
+
37
37
  bytes << byte
38
38
  bytes << " " unless i == bytesperline - 1
39
39
  end
@@ -18,7 +18,7 @@ EMBEDDED_NAME = "#{('a'..'z').to_a.sample(8).join}.pdf"
18
18
  # A simple document displaying a message box.
19
19
  #
20
20
  output_str = StringIO.new
21
- nested_doc = PDF.write(output_str) do |pdf|
21
+ PDF.write(output_str) do |pdf|
22
22
  pdf.onDocumentOpen Action::JavaScript "app.alert('Hello world!');"
23
23
  end
24
24
 
@@ -15,8 +15,8 @@ include Origami
15
15
  OUTPUT_FILE = "#{File.basename(__FILE__, ".rb")}.pdf"
16
16
 
17
17
  # Creating a new file
18
- pdf = PDF.new
19
- .onDocumentOpen(Action::JavaScript 'app.alert("Hello world");')
20
- .save(OUTPUT_FILE)
18
+ PDF.new
19
+ .onDocumentOpen(Action::JavaScript 'app.alert("Hello world");')
20
+ .save(OUTPUT_FILE)
21
21
 
22
22
  puts "PDF file saved as #{OUTPUT_FILE}."
@@ -28,7 +28,6 @@ module Origami
28
28
  enable_type_checking: true, # set to false to disable type consistency checks during compilation.
29
29
  enable_type_guessing: true, # set to false to prevent the parser to guess the type of special dictionary and streams (not recommended).
30
30
  enable_type_propagation: true, # set to false to prevent the parser to propagate type from parents to children.
31
- use_openssl: true, # set to false to use Origami crypto backend.
32
31
  ignore_bad_references: false, # set to interpret invalid references as Null objects, instead of raising an exception.
33
32
  ignore_zlib_errors: false, # set to true to ignore exceptions on invalid Flate streams.
34
33
  ignore_png_errors: false, # set to true to ignore exceptions on invalid PNG predictors.
@@ -26,7 +26,7 @@ module Origami
26
26
  # Returns true if the document contains an acrobat form.
27
27
  #
28
28
  def form?
29
- (not self.Catalog.nil?) and self.Catalog.has_key? :AcroForm
29
+ self.Catalog.key? :AcroForm
30
30
  end
31
31
 
32
32
  #
@@ -101,8 +101,8 @@ module Origami
101
101
  # Flags relative to signature fields.
102
102
  #
103
103
  module SigFlags
104
- SIGNATURESEXIST = 1 << 0
105
- APPENDONLY = 1 << 1
104
+ SIGNATURES_EXIST = 1 << 0
105
+ APPEND_ONLY = 1 << 1
106
106
  end
107
107
 
108
108
  field :Fields, :Type => Array, :Required => true, :Default => []
@@ -170,7 +170,7 @@ module Origami
170
170
  def cast_to(type, parser = nil)
171
171
  super(type)
172
172
 
173
- cast = type.new(self, parser)
173
+ cast = type.new(self.copy, parser)
174
174
  cast.parent = self.parent
175
175
  cast.no, cast.generation = self.no, self.generation
176
176
  if self.indirect?
@@ -182,8 +182,6 @@ module Origami
182
182
  cast
183
183
  end
184
184
 
185
- def self.native_type ; Origami::Array end
186
-
187
185
  #
188
186
  # Parameterized Array class with additional typing information.
189
187
  # Example: Array.of(Integer)
@@ -51,7 +51,7 @@ module Origami
51
51
  super(@value.to_s)
52
52
  end
53
53
 
54
- def self.parse(stream, parser = nil) #:nodoc:
54
+ def self.parse(stream, _parser = nil) #:nodoc:
55
55
  offset = stream.pos
56
56
 
57
57
  if stream.scan(@@regexp).nil?
@@ -73,8 +73,6 @@ module Origami
73
73
  @value
74
74
  end
75
75
 
76
- def self.native_type ; Boolean end
77
-
78
76
  def false?
79
77
  @value == false
80
78
  end
@@ -39,22 +39,16 @@ module Origami
39
39
  #
40
40
  def Catalog
41
41
  cat = trailer_key(:Root)
42
+ raise InvalidPDFError, "Broken catalog" unless cat.is_a?(Catalog)
42
43
 
43
- case cat
44
- when Catalog then
45
- cat
46
- when Dictionary then
47
- cat.cast_to(Catalog)
48
- else
49
- raise InvalidPDFError, "Broken catalog"
50
- end
44
+ cat
51
45
  end
52
46
 
53
47
  #
54
48
  # Sets the current Catalog Dictionary.
55
49
  #
56
50
  def Catalog=(cat)
57
- cat = cat.cast_to(Catalog) unless cat.is_a? Catalog
51
+ raise TypeError, "Must be a Catalog object" unless cat.is_a?(Catalog)
58
52
 
59
53
  delete_object(@revisions.last.trailer[:Root]) if @revisions.last.trailer[:Root]
60
54
 
@@ -157,7 +157,7 @@ module Origami
157
157
  # _left_, _bottom_, _right_, _top_:: The rectangle to fit in.
158
158
  #
159
159
  def self.[](page, left: 0, bottom: 0, right: 0, top: 0)
160
- self.new([pageref, :FitR, left, bottom, right, top])
160
+ self.new([page, :FitR, left, bottom, right, top])
161
161
  end
162
162
  end
163
163
 
@@ -231,7 +231,7 @@ module Origami
231
231
  # _left_:: The horizontal coord.
232
232
  #
233
233
  def self.[](page, left: 0)
234
- self.new([pageref, :FitBV, left])
234
+ self.new([page, :FitBV, left])
235
235
  end
236
236
  end
237
237
 
@@ -29,14 +29,15 @@ module Origami
29
29
  #
30
30
  class Dictionary < Hash
31
31
  include Origami::Object
32
+ include FieldAccessor
32
33
  using TypeConversion
33
34
 
34
35
  TOKENS = %w{ << >> } #:nodoc:
35
36
  @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first + WHITESPACES)
36
37
  @@regexp_close = Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)
37
38
 
38
- @@cast_fingerprints = {}
39
- @@cast_keys = []
39
+ @@type_signatures = {}
40
+ @@type_keys = []
40
41
 
41
42
  attr_reader :strings_cache, :names_cache, :xref_cache
42
43
 
@@ -92,13 +93,13 @@ module Origami
92
93
  key = Name.parse(stream, parser)
93
94
 
94
95
  type = Object.typeof(stream)
95
- raise InvalidDictionaryObjectError, "Invalid object for field #{key.to_s}" if type.nil?
96
+ raise InvalidDictionaryObjectError, "Invalid object for field #{key}" if type.nil?
96
97
 
97
98
  value = type.parse(stream, parser)
98
99
  hash[key] = value
99
100
  end
100
101
 
101
- if Origami::OPTIONS[:enable_type_guessing] and not (@@cast_keys & hash.keys).empty?
102
+ if Origami::OPTIONS[:enable_type_guessing] and not (@@type_keys & hash.keys).empty?
102
103
  dict_type = self.guess_type(hash)
103
104
  else
104
105
  dict_type = self
@@ -124,7 +125,7 @@ module Origami
124
125
  else
125
126
  content = TOKENS.first.dup
126
127
  self.each_pair do |key,value|
127
- content << "#{key.to_s} #{value.is_a?(Dictionary) ? value.to_s(indent: 0) : value.to_s}"
128
+ content << "#{key} #{value.is_a?(Dictionary) ? value.to_s(indent: 0) : value.to_s}"
128
129
  end
129
130
  content << TOKENS.last
130
131
  end
@@ -179,7 +180,7 @@ module Origami
179
180
  def cast_to(type, parser = nil)
180
181
  super(type)
181
182
 
182
- cast = type.new(self, parser)
183
+ cast = type.new(self.copy, parser)
183
184
  cast.parent = self.parent
184
185
  cast.no, cast.generation = self.no, self.generation
185
186
  if self.indirect?
@@ -198,17 +199,6 @@ module Origami
198
199
  end
199
200
  alias value to_h
200
201
 
201
- def method_missing(field, *args) #:nodoc:
202
- raise NoMethodError, "No method `#{field}' for #{self.class}" unless field.to_s[0,1] =~ /[A-Z]/
203
-
204
- if field.to_s[-1,1] == '='
205
- self[field.to_s[0..-2].to_sym] = args.first
206
- else
207
- obj = self[field];
208
- obj.is_a?(Reference) ? obj.solve : obj
209
- end
210
- end
211
-
212
202
  def copy
213
203
  copy = self.class.new
214
204
  self.each_pair do |k,v|
@@ -223,28 +213,24 @@ module Origami
223
213
  copy
224
214
  end
225
215
 
226
- def self.native_type; Dictionary end
227
-
228
- def self.add_type_info(klass, key, value) #:nodoc:
229
- raise TypeError, "Invalid class #{klass}" unless klass.is_a?(Class) and klass < Dictionary
230
-
216
+ def self.add_type_signature(key, value) #:nodoc:
231
217
  key, value = key.to_o, value.to_o
232
218
 
233
219
  # Inherit the superclass type information.
234
- if not @@cast_fingerprints.key?(klass) and @@cast_fingerprints.key?(klass.superclass)
235
- @@cast_fingerprints[klass] = @@cast_fingerprints[klass.superclass].dup
220
+ if not @@type_signatures.key?(self) and @@type_signatures.key?(self.superclass)
221
+ @@type_signatures[self] = @@type_signatures[self.superclass].dup
236
222
  end
237
223
 
238
- @@cast_fingerprints[klass] ||= {}
239
- @@cast_fingerprints[klass][key] = value
224
+ @@type_signatures[self] ||= {}
225
+ @@type_signatures[self][key] = value
240
226
 
241
- @@cast_keys.push(key) unless @@cast_keys.include?(key)
227
+ @@type_keys.push(key) unless @@type_keys.include?(key)
242
228
  end
243
229
 
244
230
  def self.guess_type(hash) #:nodoc:
245
231
  best_type = self
246
232
 
247
- @@cast_fingerprints.each_pair do |klass, keys|
233
+ @@type_signatures.each_pair do |klass, keys|
248
234
  next unless klass < best_type
249
235
 
250
236
  best_type = klass if keys.all? { |k,v| hash[k] == v }
@@ -253,7 +239,7 @@ module Origami
253
239
  best_type
254
240
  end
255
241
 
256
- def self.hint_type(name); nil end #:nodoc:
242
+ def self.hint_type(_name); nil end #:nodoc:
257
243
 
258
244
  private
259
245
 
@@ -18,12 +18,7 @@
18
18
 
19
19
  =end
20
20
 
21
- begin
22
- require 'openssl' if Origami::OPTIONS[:use_openssl]
23
- rescue LoadError
24
- Origami::OPTIONS[:use_openssl] = false
25
- end
26
-
21
+ require 'openssl'
27
22
  require 'securerandom'
28
23
  require 'digest/md5'
29
24
  require 'digest/sha2'
@@ -55,11 +50,12 @@ module Origami
55
50
  def decrypt(passwd = "")
56
51
  raise EncryptionError, "PDF is not encrypted" unless self.encrypted?
57
52
 
58
- encrypt_dict = trailer_key(:Encrypt)
59
- handler = Encryption::Standard::Dictionary.new(encrypt_dict.dup)
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)
60
56
 
61
57
  unless handler.Filter == :Standard
62
- raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter.to_s}'"
58
+ raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter}'"
63
59
  end
64
60
 
65
61
  crypt_filters = {
@@ -124,47 +120,13 @@ module Origami
124
120
  raise EncryptionInvalidPasswordError
125
121
  end
126
122
 
127
- encrypt_metadata = (handler.EncryptMetadata != false)
128
-
129
123
  self.extend(Encryption::EncryptedDocument)
130
124
  self.encryption_handler = handler
131
125
  self.crypt_filters = crypt_filters
132
126
  self.encryption_key = encryption_key
133
127
  self.stm_filter, self.str_filter = stream_filter, string_filter
134
128
 
135
- #
136
- # Should be fixed to exclude only the active XRefStream
137
- #
138
- metadata = self.Catalog.Metadata
139
-
140
- self.indirect_objects.each do |indobj|
141
- encrypted_objects = []
142
- case indobj
143
- when String,Stream then encrypted_objects << indobj
144
- when Dictionary,Array then encrypted_objects |= indobj.strings_cache
145
- end
146
-
147
- encrypted_objects.each do |obj|
148
- case obj
149
- when String
150
- next if obj.equal?(encrypt_dict[:U]) or
151
- obj.equal?(encrypt_dict[:O]) or
152
- obj.equal?(encrypt_dict[:UE]) or
153
- obj.equal?(encrypt_dict[:OE]) or
154
- obj.equal?(encrypt_dict[:Perms]) or
155
- (obj.parent.is_a?(Signature::DigitalSignature) and
156
- obj.equal?(obj.parent[:Contents]))
157
-
158
- obj.extend(Encryption::EncryptedString) unless obj.is_a?(Encryption::EncryptedString)
159
- obj.decrypt!
160
-
161
- when Stream
162
- next if obj.is_a?(XRefStream) or (not encrypt_metadata and obj.equal?(metadata))
163
-
164
- obj.extend(Encryption::EncryptedStream) unless obj.is_a?(Encryption::EncryptedStream)
165
- end
166
- end
167
- end
129
+ decrypt_objects
168
130
 
169
131
  self
170
132
  end
@@ -186,98 +148,145 @@ module Origami
186
148
  {
187
149
  :user_passwd => '',
188
150
  :owner_passwd => '',
189
- :cipher => 'rc4', # :RC4 or :AES
151
+ :cipher => 'aes', # :RC4 or :AES
190
152
  :key_size => 128, # Key size in bits
191
153
  :hardened => false, # Use newer password validation (since Reader X)
192
154
  :encrypt_metadata => true, # Metadata shall be encrypted?
193
155
  :permissions => Encryption::Standard::Permissions::ALL # Document permissions
194
156
  }.update(options)
195
157
 
196
- userpasswd, ownerpasswd = params[:user_passwd], params[:owner_passwd]
158
+ # Get the cryptographic parameters.
159
+ version, revision, crypt_filters = crypto_revision_from_options(params)
197
160
 
198
- case params[:cipher].upcase
199
- when 'RC4'
200
- algorithm = Encryption::RC4
201
- if (40..128) === params[:key_size] and params[:key_size] % 8 == 0
202
- if params[:key_size] > 40
203
- version = 2
204
- revision = 3
205
- else
206
- version = 1
207
- revision = 2
208
- end
209
- else
210
- raise EncryptionError, "Invalid RC4 key length"
211
- end
161
+ # Create the security handler.
162
+ handler, encryption_key = create_security_handler(version, revision, params)
212
163
 
213
- crypt_filters = Hash.new(algorithm)
214
- string_filter = stream_filter = nil
164
+ # Turn this document into an EncryptedDocument instance.
165
+ self.extend(Encryption::EncryptedDocument)
166
+ self.encryption_handler = handler
167
+ self.encryption_key = encryption_key
168
+ self.crypt_filters = crypt_filters
169
+ self.stm_filter = self.str_filter = :StdCF
215
170
 
216
- when 'AES'
217
- algorithm = Encryption::AES
218
- if params[:key_size] == 128
219
- version = revision = 4
220
- elsif params[:key_size] == 256
221
- version = 5
222
- if params[:hardened]
223
- revision = 6
224
- else
225
- revision = 5
226
- end
227
- else
228
- raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
229
- end
171
+ self
172
+ end
230
173
 
231
- crypt_filters = {
232
- Identity: Encryption::Identity,
233
- StdCF: algorithm
234
- }
235
- string_filter = stream_filter = :StdCF
174
+ private
236
175
 
237
- else
238
- raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
239
- end
176
+ #
177
+ # Installs the standard security dictionary, marking the document as being encrypted.
178
+ # Returns the handler and the encryption key used for protecting contents.
179
+ #
180
+ def create_security_handler(version, revision, params)
240
181
 
182
+ # Ensure the document has an ID.
241
183
  doc_id = (trailer_key(:ID) || generate_id).first
242
184
 
185
+ # Create the standard encryption dictionary.
243
186
  handler = Encryption::Standard::Dictionary.new
244
- handler.Filter = :Standard #:nodoc:
187
+ handler.Filter = :Standard
245
188
  handler.V = version
246
189
  handler.R = revision
247
190
  handler.Length = params[:key_size]
248
191
  handler.P = -1 # params[:Permissions]
249
192
 
193
+ # Build the crypt filter dictionary.
250
194
  if revision >= 4
251
195
  handler.EncryptMetadata = params[:encrypt_metadata]
252
196
  handler.CF = Dictionary.new
253
- cryptfilter = Encryption::CryptFilterDictionary.new
254
- cryptfilter.AuthEvent = :DocOpen
197
+ crypt_filter = Encryption::CryptFilterDictionary.new
198
+ crypt_filter.AuthEvent = :DocOpen
255
199
 
256
200
  if revision == 4
257
- cryptfilter.CFM = :AESV2
201
+ crypt_filter.CFM = :AESV2
258
202
  else
259
- cryptfilter.CFM = :AESV3
203
+ crypt_filter.CFM = :AESV3
260
204
  end
261
205
 
262
- cryptfilter.Length = params[:key_size] >> 3
206
+ crypt_filter.Length = params[:key_size] >> 3
263
207
 
264
- handler.CF[:StdCF] = cryptfilter
208
+ handler.CF[:StdCF] = crypt_filter
265
209
  handler.StmF = handler.StrF = :StdCF
266
210
  end
267
211
 
268
- handler.set_passwords(ownerpasswd, userpasswd, doc_id)
269
- encryption_key = handler.compute_user_encryption_key(userpasswd, doc_id)
212
+ user_passwd, owner_passwd = params[:user_passwd], params[:owner_passwd]
270
213
 
271
- file_info = get_trailer_info
272
- file_info[:Encrypt] = self << handler
214
+ # Setup keys.
215
+ handler.set_passwords(owner_passwd, user_passwd, doc_id)
216
+ encryption_key = handler.compute_user_encryption_key(user_passwd, doc_id)
273
217
 
274
- self.extend(Encryption::EncryptedDocument)
275
- self.encryption_handler = handler
276
- self.encryption_key = encryption_key
277
- self.crypt_filters = crypt_filters
278
- self.stm_filter = self.str_filter = :StdCF
218
+ # Install the encryption dictionary to the document.
219
+ self.trailer.Encrypt = self << handler
279
220
 
280
- self
221
+ [ handler, encryption_key ]
222
+ end
223
+
224
+ #
225
+ # Converts the parameters passed to PDF#encrypt.
226
+ # Returns [ version, revision, crypt_filters ]
227
+ #
228
+ def crypto_revision_from_options(params)
229
+ case params[:cipher].upcase
230
+ when 'RC4'
231
+ algorithm = Encryption::RC4
232
+ version, revision = crypto_revision_from_rc4_key(params[:key_size])
233
+ crypt_filters = Hash.new(algorithm)
234
+
235
+ when 'AES'
236
+ algorithm = Encryption::AES
237
+ version, revision = crypto_revision_from_aes_key(params[:key_size], params[:hardened])
238
+
239
+ crypt_filters = {
240
+ Identity: Encryption::Identity,
241
+ StdCF: algorithm
242
+ }
243
+ else
244
+ raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
245
+ end
246
+
247
+ [ version, revision, crypt_filters ]
248
+ end
249
+
250
+ #
251
+ # Compute the required standard security handler version based on the RC4 key size.
252
+ # _key_size_:: Key size in bits.
253
+ # Returns [ version, revision ].
254
+ #
255
+ def crypto_revision_from_rc4_key(key_size)
256
+ raise EncryptionError, "Invalid RC4 key length" unless (40..128) === key_size and key_size % 8 == 0
257
+
258
+ if key_size > 40
259
+ version = 2
260
+ revision = 3
261
+ else
262
+ version = 1
263
+ revision = 2
264
+ end
265
+
266
+ [ version, revision ]
267
+ end
268
+
269
+ #
270
+ # Compute the required standard security handler version based on the AES key size.
271
+ # _key_size_:: Key size in bits.
272
+ # _hardened_:: Use the extension level 8 hardened derivation algorithm.
273
+ # Returns [ version, revision ].
274
+ #
275
+ def crypto_revision_from_aes_key(key_size, hardened)
276
+ if key_size == 128
277
+ version = revision = 4
278
+ elsif key_size == 256
279
+ version = 5
280
+ if hardened
281
+ revision = 6
282
+ else
283
+ revision = 5
284
+ end
285
+ else
286
+ raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
287
+ end
288
+
289
+ [ version, revision ]
281
290
  end
282
291
  end
283
292
 
@@ -297,11 +306,7 @@ module Origami
297
306
  # Generates _n_ random bytes from a crypto PRNG.
298
307
  #
299
308
  def self.strong_rand_bytes(n)
300
- if Origami::OPTIONS[:use_openssl]
301
- OpenSSL::Random.random_bytes(n)
302
- else
303
- SecureRandom.random_bytes(n)
304
- end
309
+ SecureRandom.random_bytes(n)
305
310
  end
306
311
 
307
312
  module EncryptedDocument
@@ -327,90 +332,91 @@ module Origami
327
332
 
328
333
  private
329
334
 
330
- def physicalize(options = {})
331
-
332
- build = -> (obj, revision) do
333
- if obj.is_a?(EncryptedObject)
334
- if options[:decrypt] == true
335
- obj.pre_build
336
- obj.decrypt!
337
- obj.decrypted = false # makes it believe no encryption pass is required
338
- obj.post_build
339
-
340
- return
341
- end
342
- end
335
+ #
336
+ # For each object subject to encryption, convert it to an EncryptedObject and decrypt it if necessary.
337
+ #
338
+ def decrypt_objects
339
+ each_encryptable_object do |object|
340
+ case object
341
+ when String
342
+ object.extend(EncryptedString) unless object.is_a?(EncryptedString)
343
+ object.decrypt!
343
344
 
344
- if obj.is_a?(ObjectStream)
345
- obj.each do |subobj|
346
- build.call(subobj, revision)
347
- end
345
+ when Stream
346
+ object.extend(EncryptedStream) unless object.is_a?(EncryptedStream)
348
347
  end
348
+ end
349
+ end
349
350
 
350
- obj.pre_build
351
-
352
- case obj
351
+ #
352
+ # For each object subject to encryption, convert it to an EncryptedObject and mark it as not encrypted yet.
353
+ #
354
+ def encrypt_objects
355
+ each_encryptable_object do |object|
356
+ case object
353
357
  when String
354
- if not obj.equal?(@encryption_handler[:U]) and
355
- not obj.equal?(@encryption_handler[:O]) and
356
- not obj.equal?(@encryption_handler[:UE]) and
357
- not obj.equal?(@encryption_handler[:OE]) and
358
- not obj.equal?(@encryption_handler[:Perms]) and
359
- not (obj.parent.is_a?(Signature::DigitalSignature) and
360
- obj.equal?(obj.parent[:Contents])) and
361
- not obj.indirect_parent.parent.is_a?(ObjectStream)
362
-
363
- unless obj.is_a?(EncryptedString)
364
- obj.extend(EncryptedString)
365
- obj.decrypted = true
366
- end
358
+ unless object.is_a?(EncryptedString)
359
+ object.extend(EncryptedString)
360
+ object.decrypted = true
367
361
  end
368
362
 
369
363
  when Stream
370
- return if obj.is_a?(XRefStream)
371
- return if obj.equal?(self.Catalog.Metadata) and not @encryption_handler.EncryptMetadata
372
-
373
- unless obj.is_a?(EncryptedStream)
374
- obj.extend(EncryptedStream)
375
- obj.decrypted = true
376
- end
377
-
378
- when Dictionary, Array
379
- obj.map! do |subobj|
380
- if subobj.indirect?
381
- if get_object(subobj.reference)
382
- subobj.reference
383
- else
384
- ref = add_to_revision(subobj, revision)
385
- build.call(subobj, revision)
386
- ref
387
- end
388
- else
389
- subobj
390
- end
364
+ unless object.is_a?(EncryptedStream)
365
+ object.extend(EncryptedStream)
366
+ object.decrypted = true
391
367
  end
368
+ end
369
+ end
370
+ end
392
371
 
393
- obj.each do |subobj|
394
- build.call(subobj, revision)
372
+ #
373
+ # Iterates over each encryptable objects in the document.
374
+ #
375
+ def each_encryptable_object(&b)
376
+
377
+ # Metadata may not be encrypted depending on the security handler configuration.
378
+ encrypt_metadata = (@encryption_handler.EncryptMetadata != false)
379
+ metadata = self.Catalog.Metadata
380
+
381
+ self.each_object(recursive: true)
382
+ .lazy
383
+ .select { |object|
384
+ case object
385
+ when Stream
386
+ not object.is_a?(XRefStream) or (encrypt_metadata and object.equal?(metadata))
387
+ when String
388
+ not object.parent.equal?(@encryption_handler)
395
389
  end
396
- end
390
+ }
391
+ .each(&b)
392
+ end
397
393
 
398
- obj.post_build
399
- end
394
+ def physicalize(options = {})
395
+ encrypt_objects
400
396
 
401
- # stack up every root objects
402
- indirect_objects_by_rev.each do |obj, revision|
403
- build.call(obj, revision)
404
- end
397
+ super
405
398
 
406
399
  # remove encrypt dictionary if requested
407
400
  if options[:decrypt]
408
- delete_object(get_trailer_info[:Encrypt])
409
- get_trailer_info[:Encrypt] = nil
401
+ delete_object(self.trailer[:Encrypt])
402
+ self.trailer[:Encrypt] = nil
410
403
  end
411
404
 
412
405
  self
413
406
  end
407
+
408
+ def build_object(object, revision, options)
409
+ if object.is_a?(EncryptedObject) and options[:decrypt]
410
+ object.pre_build
411
+ object.decrypt!
412
+ object.decrypted = false # makes it believe no encryption pass is required
413
+ object.post_build
414
+
415
+ return
416
+ end
417
+
418
+ super
419
+ end
414
420
  end
415
421
 
416
422
  #
@@ -438,7 +444,7 @@ module Origami
438
444
  no, gen = parent.no, parent.generation
439
445
  k = encryption_key + [no].pack("I")[0..2] + [gen].pack("I")[0..1]
440
446
 
441
- key_len = (k.length > 16) ? 16 : k.length
447
+ key_len = [k.length, 16].min
442
448
  k << "sAlT" if cipher == Encryption::AES
443
449
 
444
450
  Digest::MD5.digest(k)[0, key_len]
@@ -512,21 +518,7 @@ module Origami
512
518
 
513
519
  encode!
514
520
 
515
- if self.filters.first == :Crypt
516
- params = decode_params.first
517
-
518
- if params.is_a?(Dictionary) and params.Name.is_a?(Name)
519
- crypt_filter = params.Name.value
520
- else
521
- crypt_filter = :Identity
522
- end
523
-
524
- cipher = self.document.encryption_cipher(crypt_filter)
525
- else
526
- cipher = self.document.stream_encryption_cipher
527
- end
528
- raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
529
-
521
+ cipher = get_encryption_cipher
530
522
  key = compute_object_key(cipher)
531
523
 
532
524
  @encoded_data =
@@ -548,6 +540,22 @@ module Origami
548
540
  def decrypt!
549
541
  return self if @decrypted
550
542
 
543
+ cipher = get_encryption_cipher
544
+ key = compute_object_key(cipher)
545
+
546
+ self.encoded_data = cipher.decrypt(key, @encoded_data)
547
+ @decrypted = true
548
+
549
+ self
550
+ end
551
+
552
+ private
553
+
554
+ #
555
+ # Get the stream encryption cipher.
556
+ # The cipher used may depend on the presence of a Crypt filter.
557
+ #
558
+ def get_encryption_cipher
551
559
  if self.filters.first == :Crypt
552
560
  params = decode_params.first
553
561
 
@@ -561,14 +569,10 @@ module Origami
561
569
  else
562
570
  cipher = self.document.stream_encryption_cipher
563
571
  end
564
- raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
565
-
566
- key = compute_object_key(cipher)
567
572
 
568
- self.encoded_data = cipher.decrypt(key, @encoded_data)
569
- @decrypted = true
573
+ raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
570
574
 
571
- self
575
+ cipher
572
576
  end
573
577
  end
574
578
 
@@ -576,17 +580,17 @@ module Origami
576
580
  # Identity transformation.
577
581
  #
578
582
  module Identity
579
- def Identity.encrypt(key, data)
583
+ def Identity.encrypt(_key, data)
580
584
  data
581
585
  end
582
586
 
583
- def Identity.decrypt(key, data)
587
+ def Identity.decrypt(_key, data)
584
588
  data
585
589
  end
586
590
  end
587
591
 
588
592
  #
589
- # Pure Ruby implementation of the RC4 symmetric algorithm
593
+ # Class wrapper for the RC4 algorithm.
590
594
  #
591
595
  class RC4
592
596
 
@@ -608,140 +612,31 @@ module Origami
608
612
  # Creates and initialises a new RC4 generator using given key
609
613
  #
610
614
  def initialize(key)
611
- if Origami::OPTIONS[:use_openssl]
612
- @key = key
613
- else
614
- @state = init(key)
615
- end
615
+ @key = key
616
616
  end
617
617
 
618
618
  #
619
619
  # Encrypt/decrypt data with the RC4 encryption algorithm
620
620
  #
621
621
  def cipher(data)
622
- return "" if data.empty?
623
-
624
- if Origami::OPTIONS[:use_openssl]
625
- rc4 = OpenSSL::Cipher::RC4.new.encrypt
626
- rc4.key_len = @key.length
627
- rc4.key = @key
628
-
629
- output = rc4.update(data) << rc4.final
630
- else
631
- output = ""
632
- i, j = 0, 0
633
- data.each_byte do |byte|
634
- i = i.succ & 0xFF
635
- j = (j + @state[i]) & 0xFF
622
+ return '' if data.empty?
636
623
 
637
- @state[i], @state[j] = @state[j], @state[i]
624
+ rc4 = OpenSSL::Cipher::RC4.new.encrypt
625
+ rc4.key_len = @key.length
626
+ rc4.key = @key
638
627
 
639
- output << (@state[@state[i] + @state[j] & 0xFF] ^ byte).chr
640
- end
641
- end
642
-
643
- output
628
+ rc4.update(data) + rc4.final
644
629
  end
645
630
 
646
631
  alias encrypt cipher
647
632
  alias decrypt cipher
648
-
649
- private
650
-
651
- def init(key) #:nodoc:
652
- state = (0..255).to_a
653
-
654
- j = 0
655
- 256.times do |i|
656
- j = ( j + state[i] + key[i % key.size].ord ) & 0xFF
657
- state[i], state[j] = state[j], state[i]
658
- end
659
-
660
- state
661
- end
662
633
  end
663
634
 
664
635
  #
665
- # Pure Ruby implementation of the AES symmetric algorithm.
666
- # Using mode CBC.
636
+ # Class wrapper for AES mode CBC.
667
637
  #
668
638
  class AES
669
- NROWS = 4
670
- NCOLS = 4
671
- BLOCKSIZE = NROWS * NCOLS
672
-
673
- ROUNDS =
674
- {
675
- 16 => 10,
676
- 24 => 12,
677
- 32 => 14
678
- }
679
-
680
- #
681
- # Rijndael S-box
682
- #
683
- SBOX =
684
- [
685
- 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
686
- 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
687
- 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
688
- 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
689
- 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
690
- 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
691
- 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
692
- 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
693
- 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
694
- 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
695
- 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
696
- 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
697
- 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
698
- 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
699
- 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
700
- 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
701
- ]
702
-
703
- #
704
- # Inverse of the Rijndael S-box
705
- #
706
- RSBOX =
707
- [
708
- 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
709
- 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
710
- 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
711
- 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
712
- 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
713
- 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
714
- 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
715
- 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
716
- 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
717
- 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
718
- 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
719
- 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
720
- 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
721
- 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
722
- 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
723
- 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
724
- ]
725
-
726
- RCON =
727
- [
728
- 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
729
- 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
730
- 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
731
- 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
732
- 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
733
- 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
734
- 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
735
- 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
736
- 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
737
- 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
738
- 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
739
- 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
740
- 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
741
- 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
742
- 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
743
- 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb
744
- ]
639
+ BLOCKSIZE = 16
745
640
 
746
641
  attr_writer :iv
747
642
 
@@ -754,7 +649,7 @@ module Origami
754
649
  end
755
650
 
756
651
  def initialize(key, iv, use_padding = true)
757
- unless key.size == 16 or key.size == 24 or key.size == 32
652
+ unless [16, 24, 32].include?(key.size)
758
653
  raise EncryptionError, "Key must have a length of 128, 192 or 256 bits."
759
654
  end
760
655
 
@@ -777,35 +672,12 @@ module Origami
777
672
  data << (padlen.chr * padlen)
778
673
  end
779
674
 
780
- if Origami::OPTIONS[:use_openssl]
781
- aes = OpenSSL::Cipher::Cipher.new("aes-#{@key.length << 3}-cbc").encrypt
782
- aes.iv = @iv
783
- aes.key = @key
784
- aes.padding = 0
675
+ aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").encrypt
676
+ aes.iv = @iv
677
+ aes.key = @key
678
+ aes.padding = 0
785
679
 
786
- @iv + aes.update(data) + aes.final
787
- else
788
- cipher = []
789
- cipherblock = []
790
- nblocks = data.size / BLOCKSIZE
791
-
792
- first_round = true
793
- nblocks.times do |n|
794
- plainblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
795
-
796
- if first_round
797
- BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
798
- else
799
- BLOCKSIZE.times do |i| plainblock[i] ^= cipherblock[i] end
800
- end
801
-
802
- first_round = false
803
- cipherblock = aes_encrypt(plainblock)
804
- cipher.concat(cipherblock)
805
- end
806
-
807
- @iv + cipher.pack("C*")
808
- end
680
+ @iv + aes.update(data) + aes.final
809
681
  end
810
682
 
811
683
  def decrypt(data)
@@ -815,36 +687,12 @@ module Origami
815
687
 
816
688
  @iv = data.slice!(0, BLOCKSIZE)
817
689
 
818
- if Origami::OPTIONS[:use_openssl]
819
- aes = OpenSSL::Cipher::Cipher.new("aes-#{@key.length << 3}-cbc").decrypt
820
- aes.iv = @iv
821
- aes.key = @key
822
- aes.padding = 0
823
-
824
- plain = (aes.update(data) + aes.final).unpack("C*")
825
- else
826
- plain = []
827
- plainblock = []
828
- prev_cipherblock = []
829
- nblocks = data.size / BLOCKSIZE
830
-
831
- first_round = true
832
- nblocks.times do |n|
833
- cipherblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
834
-
835
- plainblock = aes_decrypt(cipherblock)
690
+ aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").decrypt
691
+ aes.iv = @iv
692
+ aes.key = @key
693
+ aes.padding = 0
836
694
 
837
- if first_round
838
- BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
839
- else
840
- BLOCKSIZE.times do |i| plainblock[i] ^= prev_cipherblock[i] end
841
- end
842
-
843
- first_round = false
844
- prev_cipherblock = cipherblock
845
- plain.concat(plainblock)
846
- end
847
- end
695
+ plain = (aes.update(data) + aes.final).unpack("C*")
848
696
 
849
697
  if @use_padding
850
698
  padlen = plain[-1]
@@ -860,223 +708,6 @@ module Origami
860
708
 
861
709
  plain.pack("C*")
862
710
  end
863
-
864
- private
865
-
866
- def rol(row, n = 1) #:nodoc
867
- n.times do row.push row.shift end ; row
868
- end
869
-
870
- def ror(row, n = 1) #:nodoc:
871
- n.times do row.unshift row.pop end ; row
872
- end
873
-
874
- def galois_mult(a, b) #:nodoc:
875
- p = 0
876
-
877
- 8.times do
878
- p ^= a if b[0] == 1
879
- highBit = a[7]
880
- a <<= 1
881
- a ^= 0x1b if highBit == 1
882
- b >>= 1
883
- end
884
-
885
- p % 256
886
- end
887
-
888
- def schedule_core(word, iter) #:nodoc:
889
- rol(word)
890
- word.map! do |byte| SBOX[byte] end
891
- word[0] ^= RCON[iter]
892
-
893
- word
894
- end
895
-
896
- def transpose(m) #:nodoc:
897
- [
898
- m[NROWS * 0, NROWS],
899
- m[NROWS * 1, NROWS],
900
- m[NROWS * 2, NROWS],
901
- m[NROWS * 3, NROWS]
902
- ].transpose.flatten
903
- end
904
-
905
- #
906
- # AES round methods.
907
- #
908
-
909
- def create_round_key(expanded_key, round = 0) #:nodoc:
910
- transpose(expanded_key[round * BLOCKSIZE, BLOCKSIZE])
911
- end
912
-
913
- def add_round_key(roundKey) #:nodoc:
914
- BLOCKSIZE.times do |i| @state[i] ^= roundKey[i] end
915
- end
916
-
917
- def sub_bytes #:nodoc:
918
- BLOCKSIZE.times do |i| @state[i] = SBOX[ @state[i] ] end
919
- end
920
-
921
- def r_sub_bytes #:nodoc:
922
- BLOCKSIZE.times do |i| @state[i] = RSBOX[ @state[i] ] end
923
- end
924
-
925
- def shift_rows #:nodoc:
926
- NROWS.times do |i|
927
- @state[i * NCOLS, NCOLS] = rol(@state[i * NCOLS, NCOLS], i)
928
- end
929
- end
930
-
931
- def r_shift_rows #:nodoc:
932
- NROWS.times do |i|
933
- @state[i * NCOLS, NCOLS] = ror(@state[i * NCOLS, NCOLS], i)
934
- end
935
- end
936
-
937
- def mix_column_with_field(column, field) #:nodoc:
938
- p = field
939
-
940
- column[0], column[1], column[2], column[3] =
941
- galois_mult(column[0], p[0]) ^
942
- galois_mult(column[3], p[1]) ^
943
- galois_mult(column[2], p[2]) ^
944
- galois_mult(column[1], p[3]),
945
-
946
- galois_mult(column[1], p[0]) ^
947
- galois_mult(column[0], p[1]) ^
948
- galois_mult(column[3], p[2]) ^
949
- galois_mult(column[2], p[3]),
950
-
951
- galois_mult(column[2], p[0]) ^
952
- galois_mult(column[1], p[1]) ^
953
- galois_mult(column[0], p[2]) ^
954
- galois_mult(column[3], p[3]),
955
-
956
- galois_mult(column[3], p[0]) ^
957
- galois_mult(column[2], p[1]) ^
958
- galois_mult(column[1], p[2]) ^
959
- galois_mult(column[0], p[3])
960
- end
961
-
962
- def mix_column(column) #:nodoc:
963
- mix_column_with_field(column, [ 2, 1, 1, 3 ])
964
- end
965
-
966
- def r_mix_column_(column) #:nodoc:
967
- mix_column_with_field(column, [ 14, 9, 13, 11 ])
968
- end
969
-
970
- def mix_columns #:nodoc:
971
- NCOLS.times do |c|
972
- column = []
973
- NROWS.times do |r| column << @state[c + r * NCOLS] end
974
- mix_column(column)
975
- NROWS.times do |r| @state[c + r * NCOLS] = column[r] end
976
- end
977
- end
978
-
979
- def r_mix_columns #:nodoc:
980
- NCOLS.times do |c|
981
- column = []
982
- NROWS.times do |r| column << @state[c + r * NCOLS] end
983
- r_mix_column_(column)
984
- NROWS.times do |r| @state[c + r * NCOLS] = column[r] end
985
- end
986
- end
987
-
988
- def expand_key(key) #:nodoc:
989
- key = key.unpack("C*")
990
- size = key.size
991
- expanded_size = 16 * (ROUNDS[key.size] + 1)
992
- rcon_iter = 1
993
- expanded_key = key[0, size]
994
-
995
- while expanded_key.size < expanded_size
996
- temp = expanded_key[-4, 4]
997
-
998
- if expanded_key.size % size == 0
999
- schedule_core(temp, rcon_iter)
1000
- rcon_iter = rcon_iter.succ
1001
- end
1002
-
1003
- temp.map! do |b| SBOX[b] end if size == 32 and expanded_key.size % size == 16
1004
-
1005
- temp.each do |b| expanded_key << (expanded_key[-size] ^ b) end
1006
- end
1007
-
1008
- expanded_key
1009
- end
1010
-
1011
- def aes_round(round_key) #:nodoc:
1012
- sub_bytes
1013
- #puts "after sub_bytes: #{@state.inspect}"
1014
- shift_rows
1015
- #puts "after shift_rows: #{@state.inspect}"
1016
- mix_columns
1017
- #puts "after mix_columns: #{@state.inspect}"
1018
- add_round_key(round_key)
1019
- #puts "roundKey = #{roundKey.inspect}"
1020
- #puts "after add_round_key: #{@state.inspect}"
1021
- end
1022
-
1023
- def r_aes_round(round_key) #:nodoc:
1024
- add_round_key(round_key)
1025
- r_mix_columns
1026
- r_shift_rows
1027
- r_sub_bytes
1028
- end
1029
-
1030
- def aes_encrypt(block) #:nodoc:
1031
- @state = transpose(block)
1032
- expanded_key = expand_key(@key)
1033
- rounds = ROUNDS[@key.size]
1034
-
1035
- aes_main(expanded_key, rounds)
1036
- end
1037
-
1038
- def aes_decrypt(block) #:nodoc:
1039
- @state = transpose(block)
1040
- expanded_key = expand_key(@key)
1041
- rounds = ROUNDS[@key.size]
1042
-
1043
- r_aes_main(expanded_key, rounds)
1044
- end
1045
-
1046
- def aes_main(expanded_key, rounds) #:nodoc:
1047
- #puts "expandedKey: #{expandedKey.inspect}"
1048
- round_key = create_round_key(expanded_key)
1049
- add_round_key(round_key)
1050
-
1051
- for i in 1..rounds-1
1052
- round_key = create_round_key(expanded_key, i)
1053
- aes_round(round_key)
1054
- end
1055
-
1056
- round_key = create_round_key(expanded_key, rounds)
1057
- sub_bytes
1058
- shift_rows
1059
- add_round_key(round_key)
1060
-
1061
- transpose(@state)
1062
- end
1063
-
1064
- def r_aes_main(expanded_key, rounds) #:nodoc:
1065
- round_key = create_round_key(expanded_key, rounds)
1066
- add_round_key(round_key)
1067
- r_shift_rows
1068
- r_sub_bytes
1069
-
1070
- (rounds - 1).downto(1) do |i|
1071
- round_key = create_round_key(expanded_key, i)
1072
- r_aes_round(round_key)
1073
- end
1074
-
1075
- round_key = create_round_key(expanded_key)
1076
- add_round_key(round_key)
1077
-
1078
- transpose(@state)
1079
- end
1080
711
  end
1081
712
 
1082
713
  #
@@ -1156,127 +787,134 @@ module Origami
1156
787
 
1157
788
  #
1158
789
  # Computes the key that will be used to encrypt/decrypt the document contents with user password.
790
+ # Called at all revisions.
1159
791
  #
1160
- def compute_user_encryption_key(userpassword, fileid)
1161
- if self.R < 5
1162
- padded = pad_password(userpassword)
1163
- padded.force_encoding('binary')
792
+ def compute_user_encryption_key(user_password, file_id)
793
+ return compute_legacy_user_encryption_key(user_password, file_id) if self.R < 5
1164
794
 
1165
- padded << self.O
1166
- padded << [ self.P ].pack("i")
795
+ passwd = password_to_utf8(user_password)
1167
796
 
1168
- padded << fileid
797
+ uks = self.U[40, 8]
1169
798
 
1170
- encrypt_metadata = self.EncryptMetadata != false
1171
- padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata
799
+ if self.R == 5
800
+ ukey = Digest::SHA256.digest(passwd + uks)
801
+ else
802
+ ukey = compute_hardened_hash(passwd, uks)
803
+ end
1172
804
 
1173
- key = Digest::MD5.digest(padded)
805
+ iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
806
+ AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
807
+ end
1174
808
 
1175
- 50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
809
+ #
810
+ # Computes the key that will be used to encrypt/decrypt the document contents.
811
+ # Only for Revision 4 and less.
812
+ #
813
+ def compute_legacy_user_encryption_key(user_password, file_id)
814
+ padded = pad_password(user_password)
815
+ padded.force_encoding('binary')
1176
816
 
1177
- if self.R == 2
1178
- key[0, 5]
1179
- elsif self.R >= 3
1180
- key[0, self.Length / 8]
1181
- end
1182
- else
1183
- passwd = password_to_utf8(userpassword)
817
+ padded << self.O
818
+ padded << [ self.P ].pack("i")
1184
819
 
1185
- uks = self.U[40, 8]
820
+ padded << file_id
1186
821
 
1187
- if self.R == 5
1188
- ukey = Digest::SHA256.digest(passwd + uks)
1189
- else
1190
- ukey = compute_hardened_hash(passwd, uks)
1191
- end
822
+ encrypt_metadata = self.EncryptMetadata != false
823
+ padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata
1192
824
 
1193
- iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1194
- AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
1195
- end
825
+ key = Digest::MD5.digest(padded)
826
+
827
+ 50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
828
+
829
+ truncate_key(key)
1196
830
  end
1197
831
 
1198
832
  #
1199
833
  # Computes the key that will be used to encrypt/decrypt the document contents with owner password.
1200
834
  # Revision 5 and above.
1201
835
  #
1202
- def compute_owner_encryption_key(ownerpassword)
1203
- if self.R >= 5
1204
- passwd = password_to_utf8(ownerpassword)
836
+ def compute_owner_encryption_key(owner_password)
837
+ return if self.R < 5
1205
838
 
1206
- oks = self.O[40, 8]
839
+ passwd = password_to_utf8(owner_password)
840
+ oks = self.O[40, 8]
1207
841
 
1208
- if self.R == 5
1209
- okey = Digest::SHA256.digest(passwd + oks + self.U)
1210
- else
1211
- okey = compute_hardened_hash(passwd, oks, self.U)
1212
- end
1213
-
1214
- iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1215
- AES.new(okey, nil, false).decrypt(iv + self.OE.value)
842
+ if self.R == 5
843
+ okey = Digest::SHA256.digest(passwd + oks + self.U)
844
+ else
845
+ okey = compute_hardened_hash(passwd, oks, self.U)
1216
846
  end
847
+
848
+ iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
849
+ AES.new(okey, nil, false).decrypt(iv + self.OE.value)
1217
850
  end
1218
851
 
1219
852
  #
1220
853
  # Set up document passwords.
1221
854
  #
1222
- def set_passwords(ownerpassword, userpassword, salt = nil)
1223
- if self.R < 5
1224
- key = compute_owner_key(ownerpassword)
1225
- upadded = pad_password(userpassword)
855
+ def set_passwords(owner_password, user_password, salt = nil)
856
+ return set_legacy_passwords(owner_password, user_password, salt) if self.R < 5
1226
857
 
1227
- owner_key = RC4.encrypt(key, upadded)
1228
- 19.times { |i| owner_key = RC4.encrypt(xor(key,i+1), owner_key) } if self.R >= 3
858
+ upass = password_to_utf8(user_password)
859
+ opass = password_to_utf8(owner_password)
1229
860
 
1230
- self.O = owner_key
1231
- self.U = compute_user_password(userpassword, salt)
861
+ uvs, uks, ovs, oks = ::Array.new(4) { Encryption.rand_bytes(8) }
862
+ file_key = Encryption.strong_rand_bytes(32)
863
+ iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1232
864
 
865
+ if self.R == 5
866
+ self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
867
+ self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
868
+ ukey = Digest::SHA256.digest(upass + uks)
869
+ okey = Digest::SHA256.digest(opass + oks + self.U)
1233
870
  else
1234
- upass = password_to_utf8(userpassword)
1235
- opass = password_to_utf8(ownerpassword)
1236
-
1237
- uvs, uks, ovs, oks = ::Array.new(4) { Encryption.rand_bytes(8) }
1238
- file_key = Encryption.strong_rand_bytes(32)
1239
- iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1240
-
1241
- if self.R == 5
1242
- self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
1243
- self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
1244
- ukey = Digest::SHA256.digest(upass + uks)
1245
- okey = Digest::SHA256.digest(opass + oks + self.U)
1246
- else
1247
- self.U = compute_hardened_hash(upass, uvs) + uvs + uks
1248
- self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
1249
- ukey = compute_hardened_hash(upass, uks)
1250
- okey = compute_hardened_hash(opass, oks, self.U)
1251
- end
871
+ self.U = compute_hardened_hash(upass, uvs) + uvs + uks
872
+ self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
873
+ ukey = compute_hardened_hash(upass, uks)
874
+ okey = compute_hardened_hash(opass, oks, self.U)
875
+ end
1252
876
 
1253
- self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
1254
- self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
877
+ self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
878
+ self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
1255
879
 
1256
- perms =
1257
- [ self.P ].pack("V") + # 0-3
1258
- [ -1 ].pack("V") + # 4-7
1259
- (self.EncryptMetadata == true ? "T" : "F") + # 8
1260
- "adb" + # 9-11
1261
- [ 0 ].pack("V") # 12-15
880
+ perms =
881
+ [ self.P ].pack("V") + # 0-3
882
+ [ -1 ].pack("V") + # 4-7
883
+ (self.EncryptMetadata == true ? "T" : "F") + # 8
884
+ "adb" + # 9-11
885
+ [ 0 ].pack("V") # 12-15
1262
886
 
1263
- self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
887
+ self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
1264
888
 
1265
- file_key
1266
- end
889
+ file_key
890
+ end
891
+
892
+ #
893
+ # Set up document passwords.
894
+ # Only for Revision 4 and less.
895
+ #
896
+ def set_legacy_passwords(owner_password, user_password, salt)
897
+ owner_key = compute_owner_key(owner_password)
898
+ upadded = pad_password(user_password)
899
+
900
+ owner_key_hash = RC4.encrypt(owner_key, upadded)
901
+ 19.times { |i| owner_key_hash = RC4.encrypt(xor(owner_key, i + 1), owner_key_hash) } if self.R >= 3
902
+
903
+ self.O = owner_key_hash
904
+ self.U = compute_user_password_hash(user_password, salt)
1267
905
  end
1268
906
 
1269
907
  #
1270
908
  # Checks user password.
1271
- # For version 2,3 and 4, _salt_ is the document ID.
909
+ # For version 2, 3 and 4, _salt_ is the document ID.
1272
910
  # For version 5 and 6, _salt_ is the User Key Salt.
1273
911
  #
1274
912
  def is_user_password?(pass, salt)
1275
913
 
1276
914
  if self.R == 2
1277
- compute_user_password(pass, salt) == self.U
915
+ compute_user_password_hash(pass, salt) == self.U
1278
916
  elsif self.R == 3 or self.R == 4
1279
- compute_user_password(pass, salt)[0, 16] == self.U[0, 16]
917
+ compute_user_password_hash(pass, salt)[0, 16] == self.U[0, 16]
1280
918
  elsif self.R == 5
1281
919
  uvs = self.U[32, 8]
1282
920
  Digest::SHA256.digest(password_to_utf8(pass) + uvs) == self.U[0, 32]
@@ -1309,9 +947,9 @@ module Origami
1309
947
  # Retrieve user password from owner password.
1310
948
  # Cannot be used with revision 5.
1311
949
  #
1312
- def retrieve_user_password(ownerpassword)
950
+ def retrieve_user_password(owner_password)
1313
951
 
1314
- key = compute_owner_key(ownerpassword)
952
+ key = compute_owner_key(owner_password)
1315
953
 
1316
954
  if self.R == 2
1317
955
  RC4.decrypt(key, self.O)
@@ -1330,31 +968,27 @@ module Origami
1330
968
  # Rev 2,3,4: O = crypt(user_pass, owner_key).
1331
969
  # Rev 5: unused.
1332
970
  #
1333
- def compute_owner_key(ownerpassword) #:nodoc:
971
+ def compute_owner_key(owner_password) #:nodoc:
1334
972
 
1335
- opadded = pad_password(ownerpassword)
973
+ opadded = pad_password(owner_password)
1336
974
 
1337
- hash = Digest::MD5.digest(opadded)
1338
- 50.times { hash = Digest::MD5.digest(hash) } if self.R >= 3
975
+ owner_key = Digest::MD5.digest(opadded)
976
+ 50.times { owner_key = Digest::MD5.digest(owner_key) } if self.R >= 3
1339
977
 
1340
- if self.R == 2
1341
- hash[0, 5]
1342
- elsif self.R >= 3
1343
- hash[0, self.Length / 8]
1344
- end
978
+ truncate_key(owner_key)
1345
979
  end
1346
980
 
1347
981
  #
1348
982
  # Compute the value of the U field.
1349
983
  # Cannot be used with revision 5.
1350
984
  #
1351
- def compute_user_password(userpassword, salt) #:nodoc:
985
+ def compute_user_password_hash(user_password, salt) #:nodoc:
1352
986
 
1353
987
  if self.R == 2
1354
- key = compute_user_encryption_key(userpassword, salt)
988
+ key = compute_user_encryption_key(user_password, salt)
1355
989
  user_key = RC4.encrypt(key, PADDING)
1356
990
  elsif self.R == 3 or self.R == 4
1357
- key = compute_user_encryption_key(userpassword, salt)
991
+ key = compute_user_encryption_key(user_password, salt)
1358
992
 
1359
993
  upadded = PADDING + salt
1360
994
  hash = Digest::MD5.digest(upadded)
@@ -1382,14 +1016,10 @@ module Origami
1382
1016
 
1383
1017
  block = input[0, block_size]
1384
1018
 
1385
- if Origami::OPTIONS[:use_openssl]
1386
- aes = OpenSSL::Cipher::Cipher.new("aes-128-cbc").encrypt
1387
- aes.iv = iv
1388
- aes.key = key
1389
- aes.padding = 0
1390
- else
1391
- fail "You need OpenSSL support to encrypt/decrypt documents with this method"
1392
- end
1019
+ aes = OpenSSL::Cipher.new("aes-128-cbc").encrypt
1020
+ aes.iv = iv
1021
+ aes.key = key
1022
+ aes.padding = 0
1393
1023
 
1394
1024
  64.times do |j|
1395
1025
  x = ''
@@ -1416,13 +1046,25 @@ module Origami
1416
1046
  h[0, 32]
1417
1047
  end
1418
1048
 
1049
+ #
1050
+ # Some revision handlers require different key sizes.
1051
+ # Revision 2 uses 40-bit keys.
1052
+ # Revisions 3 and higher rely on the Length field for the key size.
1053
+ #
1054
+ def truncate_key(key)
1055
+ if self.R == 2
1056
+ key[0, 5]
1057
+ elsif self.R >= 3
1058
+ key[0, self.Length / 8]
1059
+ end
1060
+ end
1061
+
1419
1062
  def xor(str, byte) #:nodoc:
1420
- str.split(//).map!{|c| (c[0].ord ^ byte).chr }.join
1063
+ str.bytes.map!{|b| b ^ byte }.pack("C*")
1421
1064
  end
1422
1065
 
1423
1066
  def pad_password(password) #:nodoc:
1424
- return PADDING.dup if password.empty? # Fix for Ruby 1.9 bug
1425
- password[0,32].ljust(32, PADDING)
1067
+ password[0, 32].ljust(32, PADDING)
1426
1068
  end
1427
1069
 
1428
1070
  def password_to_utf8(passwd) #:nodoc: