origami 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/bin/gui/config.rb +2 -1
- data/bin/gui/file.rb +118 -240
- data/bin/gui/gtkhex.rb +5 -5
- data/bin/gui/hexview.rb +20 -16
- data/bin/gui/imgview.rb +1 -1
- data/bin/gui/menu.rb +138 -158
- data/bin/gui/properties.rb +46 -48
- data/bin/gui/signing.rb +183 -214
- data/bin/gui/textview.rb +1 -1
- data/bin/gui/treeview.rb +13 -7
- data/bin/gui/walker.rb +102 -71
- data/bin/gui/xrefs.rb +1 -1
- data/bin/pdf2ruby +3 -3
- data/bin/pdfcop +18 -11
- data/bin/pdfextract +14 -5
- data/bin/pdfmetadata +3 -3
- data/bin/shell/console.rb +8 -8
- data/bin/shell/hexdump.rb +4 -4
- data/examples/attachments/nested_document.rb +1 -1
- data/examples/javascript/hello_world.rb +3 -3
- data/lib/origami.rb +0 -1
- data/lib/origami/acroform.rb +3 -3
- data/lib/origami/array.rb +1 -3
- data/lib/origami/boolean.rb +1 -3
- data/lib/origami/catalog.rb +3 -9
- data/lib/origami/destinations.rb +2 -2
- data/lib/origami/dictionary.rb +15 -29
- data/lib/origami/encryption.rb +334 -692
- data/lib/origami/extensions/fdf.rb +3 -2
- data/lib/origami/extensions/ppklite.rb +5 -9
- data/lib/origami/filespec.rb +2 -2
- data/lib/origami/filters.rb +54 -36
- data/lib/origami/filters/ascii.rb +67 -49
- data/lib/origami/filters/ccitt.rb +4 -236
- data/lib/origami/filters/ccitt/tables.rb +267 -0
- data/lib/origami/filters/crypt.rb +1 -1
- data/lib/origami/filters/dct.rb +0 -1
- data/lib/origami/filters/flate.rb +3 -43
- data/lib/origami/filters/lzw.rb +62 -99
- data/lib/origami/filters/predictors.rb +135 -105
- data/lib/origami/filters/runlength.rb +34 -22
- data/lib/origami/graphics.rb +2 -2
- data/lib/origami/graphics/colors.rb +89 -63
- data/lib/origami/graphics/path.rb +14 -14
- data/lib/origami/graphics/patterns.rb +31 -33
- data/lib/origami/graphics/render.rb +0 -1
- data/lib/origami/graphics/state.rb +9 -9
- data/lib/origami/graphics/text.rb +17 -17
- data/lib/origami/graphics/xobject.rb +102 -92
- data/lib/origami/javascript.rb +91 -68
- data/lib/origami/linearization.rb +22 -20
- data/lib/origami/metadata.rb +1 -1
- data/lib/origami/name.rb +1 -3
- data/lib/origami/null.rb +1 -3
- data/lib/origami/numeric.rb +3 -13
- data/lib/origami/object.rb +100 -72
- data/lib/origami/page.rb +24 -28
- data/lib/origami/parser.rb +34 -51
- data/lib/origami/parsers/fdf.rb +2 -2
- data/lib/origami/parsers/pdf.rb +41 -18
- data/lib/origami/parsers/pdf/lazy.rb +83 -46
- data/lib/origami/parsers/pdf/linear.rb +19 -10
- data/lib/origami/parsers/ppklite.rb +1 -1
- data/lib/origami/pdf.rb +150 -206
- data/lib/origami/reference.rb +4 -6
- data/lib/origami/signature.rb +76 -48
- data/lib/origami/stream.rb +69 -63
- data/lib/origami/string.rb +2 -19
- data/lib/origami/trailer.rb +25 -22
- data/lib/origami/version.rb +1 -1
- data/lib/origami/xfa.rb +6 -4
- data/lib/origami/xreftable.rb +29 -29
- data/test/test_annotations.rb +16 -38
- data/test/test_pdf_attachment.rb +1 -1
- data/test/test_pdf_parse.rb +1 -1
- data/test/test_xrefs.rb +2 -2
- metadata +4 -4
- data/lib/origami/export.rb +0 -247
data/bin/pdfmetadata
CHANGED
@@ -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
|
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
|
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
|
|
data/bin/shell/console.rb
CHANGED
@@ -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
|
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
|
89
|
+
@trailer.dictionary.each_pair do |entry, value|
|
90
90
|
print " [*] ".magenta
|
91
|
-
print "#{entry
|
92
|
-
puts "#{value
|
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
|
114
|
+
@revisions.each do |revision|
|
115
115
|
revision.to_s
|
116
|
-
|
116
|
+
end
|
117
117
|
puts
|
118
118
|
end
|
119
119
|
|
data/bin/shell/hexdump.rb
CHANGED
@@ -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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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}."
|
data/lib/origami.rb
CHANGED
@@ -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.
|
data/lib/origami/acroform.rb
CHANGED
@@ -26,7 +26,7 @@ module Origami
|
|
26
26
|
# Returns true if the document contains an acrobat form.
|
27
27
|
#
|
28
28
|
def form?
|
29
|
-
|
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
|
-
|
105
|
-
|
104
|
+
SIGNATURES_EXIST = 1 << 0
|
105
|
+
APPEND_ONLY = 1 << 1
|
106
106
|
end
|
107
107
|
|
108
108
|
field :Fields, :Type => Array, :Required => true, :Default => []
|
data/lib/origami/array.rb
CHANGED
@@ -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)
|
data/lib/origami/boolean.rb
CHANGED
@@ -51,7 +51,7 @@ module Origami
|
|
51
51
|
super(@value.to_s)
|
52
52
|
end
|
53
53
|
|
54
|
-
def self.parse(stream,
|
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
|
data/lib/origami/catalog.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
|
data/lib/origami/destinations.rb
CHANGED
@@ -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([
|
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([
|
234
|
+
self.new([page, :FitBV, left])
|
235
235
|
end
|
236
236
|
end
|
237
237
|
|
data/lib/origami/dictionary.rb
CHANGED
@@ -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
|
-
@@
|
39
|
-
@@
|
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
|
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 (@@
|
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
|
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.
|
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 @@
|
235
|
-
@@
|
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
|
-
@@
|
239
|
-
@@
|
224
|
+
@@type_signatures[self] ||= {}
|
225
|
+
@@type_signatures[self][key] = value
|
240
226
|
|
241
|
-
@@
|
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
|
-
@@
|
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(
|
242
|
+
def self.hint_type(_name); nil end #:nodoc:
|
257
243
|
|
258
244
|
private
|
259
245
|
|
data/lib/origami/encryption.rb
CHANGED
@@ -18,12 +18,7 @@
|
|
18
18
|
|
19
19
|
=end
|
20
20
|
|
21
|
-
|
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
|
-
|
59
|
-
handler =
|
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
|
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 => '
|
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
|
-
|
158
|
+
# Get the cryptographic parameters.
|
159
|
+
version, revision, crypt_filters = crypto_revision_from_options(params)
|
197
160
|
|
198
|
-
|
199
|
-
|
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
|
-
|
214
|
-
|
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
|
-
|
217
|
-
|
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
|
-
|
232
|
-
Identity: Encryption::Identity,
|
233
|
-
StdCF: algorithm
|
234
|
-
}
|
235
|
-
string_filter = stream_filter = :StdCF
|
174
|
+
private
|
236
175
|
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
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
|
-
|
254
|
-
|
197
|
+
crypt_filter = Encryption::CryptFilterDictionary.new
|
198
|
+
crypt_filter.AuthEvent = :DocOpen
|
255
199
|
|
256
200
|
if revision == 4
|
257
|
-
|
201
|
+
crypt_filter.CFM = :AESV2
|
258
202
|
else
|
259
|
-
|
203
|
+
crypt_filter.CFM = :AESV3
|
260
204
|
end
|
261
205
|
|
262
|
-
|
206
|
+
crypt_filter.Length = params[:key_size] >> 3
|
263
207
|
|
264
|
-
handler.CF[:StdCF] =
|
208
|
+
handler.CF[:StdCF] = crypt_filter
|
265
209
|
handler.StmF = handler.StrF = :StdCF
|
266
210
|
end
|
267
211
|
|
268
|
-
|
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
|
-
|
272
|
-
|
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
|
-
|
275
|
-
self.
|
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
|
-
|
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
|
-
|
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
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
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
|
-
|
345
|
-
|
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
|
-
|
351
|
-
|
352
|
-
|
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
|
-
|
355
|
-
|
356
|
-
|
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
|
-
|
371
|
-
|
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
|
-
|
394
|
-
|
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
|
-
|
390
|
+
}
|
391
|
+
.each(&b)
|
392
|
+
end
|
397
393
|
|
398
|
-
|
399
|
-
|
394
|
+
def physicalize(options = {})
|
395
|
+
encrypt_objects
|
400
396
|
|
401
|
-
|
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(
|
409
|
-
|
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 =
|
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
|
-
|
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
|
-
|
569
|
-
@decrypted = true
|
573
|
+
raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
|
570
574
|
|
571
|
-
|
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(
|
583
|
+
def Identity.encrypt(_key, data)
|
580
584
|
data
|
581
585
|
end
|
582
586
|
|
583
|
-
def Identity.decrypt(
|
587
|
+
def Identity.decrypt(_key, data)
|
584
588
|
data
|
585
589
|
end
|
586
590
|
end
|
587
591
|
|
588
592
|
#
|
589
|
-
#
|
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
|
-
|
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
|
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
|
-
|
624
|
+
rc4 = OpenSSL::Cipher::RC4.new.encrypt
|
625
|
+
rc4.key_len = @key.length
|
626
|
+
rc4.key = @key
|
638
627
|
|
639
|
-
|
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
|
-
#
|
666
|
-
# Using mode CBC.
|
636
|
+
# Class wrapper for AES mode CBC.
|
667
637
|
#
|
668
638
|
class AES
|
669
|
-
|
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
|
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
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
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
|
-
|
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
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
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
|
-
|
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(
|
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
|
-
|
1166
|
-
padded << [ self.P ].pack("i")
|
795
|
+
passwd = password_to_utf8(user_password)
|
1167
796
|
|
1168
|
-
|
797
|
+
uks = self.U[40, 8]
|
1169
798
|
|
1170
|
-
|
1171
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
1178
|
-
|
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
|
-
|
820
|
+
padded << file_id
|
1186
821
|
|
1187
|
-
|
1188
|
-
|
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
|
-
|
1194
|
-
|
1195
|
-
|
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(
|
1203
|
-
if self.R
|
1204
|
-
passwd = password_to_utf8(ownerpassword)
|
836
|
+
def compute_owner_encryption_key(owner_password)
|
837
|
+
return if self.R < 5
|
1205
838
|
|
1206
|
-
|
839
|
+
passwd = password_to_utf8(owner_password)
|
840
|
+
oks = self.O[40, 8]
|
1207
841
|
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
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(
|
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
|
-
|
1228
|
-
|
858
|
+
upass = password_to_utf8(user_password)
|
859
|
+
opass = password_to_utf8(owner_password)
|
1229
860
|
|
1230
|
-
|
1231
|
-
|
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
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
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
|
-
|
1254
|
-
|
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
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
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
|
-
|
887
|
+
self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
|
1264
888
|
|
1265
|
-
|
1266
|
-
|
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
|
-
|
915
|
+
compute_user_password_hash(pass, salt) == self.U
|
1278
916
|
elsif self.R == 3 or self.R == 4
|
1279
|
-
|
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(
|
950
|
+
def retrieve_user_password(owner_password)
|
1313
951
|
|
1314
|
-
key = compute_owner_key(
|
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(
|
971
|
+
def compute_owner_key(owner_password) #:nodoc:
|
1334
972
|
|
1335
|
-
opadded = pad_password(
|
973
|
+
opadded = pad_password(owner_password)
|
1336
974
|
|
1337
|
-
|
1338
|
-
50.times {
|
975
|
+
owner_key = Digest::MD5.digest(opadded)
|
976
|
+
50.times { owner_key = Digest::MD5.digest(owner_key) } if self.R >= 3
|
1339
977
|
|
1340
|
-
|
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
|
985
|
+
def compute_user_password_hash(user_password, salt) #:nodoc:
|
1352
986
|
|
1353
987
|
if self.R == 2
|
1354
|
-
key = compute_user_encryption_key(
|
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(
|
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
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
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.
|
1063
|
+
str.bytes.map!{|b| b ^ byte }.pack("C*")
|
1421
1064
|
end
|
1422
1065
|
|
1423
1066
|
def pad_password(password) #:nodoc:
|
1424
|
-
|
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:
|