origami 1.0.4 → 1.1.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.
data/README CHANGED
@@ -1,73 +1,64 @@
1
- ################################################################################
2
- # #
3
- # Origami - Ruby PDF manipulation framework #
4
- # #
5
- ################################################################################
1
+ NAME
6
2
 
7
- :: DESCRIPTION
8
- ==============
3
+ origami
9
4
 
10
- Origami is a framework written in Ruby designed to parse, analyze, and forge PDF
11
- documents.
12
- This is *NOT* a PDF rendering library, it aims at providing a scripting tool for
13
- generating and analyzing malicious PDF files. As well, it can be used to create
14
- on-the-fly customized PDFs, or to inject evil code into already existing
15
- documents.
5
+ DESCRIPTION
16
6
 
7
+ Origami is a framework written in Ruby designed to parse, analyze, and forge
8
+ PDF documents. This is not a PDF rendering library, it aims at providing a
9
+ scripting tool for generating and analyzing malicious PDF files. As well, it
10
+ can be used to create on-the-fly customized PDFs, or to inject evil code into
11
+ already existing documents.
17
12
 
18
- :: LICENSE
19
- ==========
13
+ VERSION
20
14
 
21
- This software is distributed under the LGPL license.
22
- See the COPYING.LESSER file for more details.
15
+ 1.1
23
16
 
17
+ DEPENDENCIES
24
18
 
25
- :: RELEASE
26
- ==========
19
+ - Ruby-GTK2 (only for GUI), http://ruby-gnome2.sourceforge.jp/
27
20
 
28
- - Current : Version 1.0.4
21
+ INSTALL
29
22
 
23
+ Stable: gem install origami
24
+ Devel: hg clone https://origami-pdf.googlecode.com/hg/ origami
30
25
 
31
- :: DEPENDENCIES
32
- ===============
26
+ DIRECTORIES
33
27
 
34
- - Ruby 1.8 (actually not very tested on 1.9)
35
- - Ruby-GTK2 (only for GUI), http://ruby-gnome2.sourceforge.jp/
28
+ ``origami/``
29
+ Core scripts used to parse a PDF file. All objects and features are
30
+ provided here.
36
31
 
32
+ ``samples/``
33
+ Many samples, mostly sorted to generate specially crafted PDFs.
37
34
 
38
- :: DIRECTORIES
39
- ==============
35
+ ``bin/``
36
+ Useful tools based on Origami.
40
37
 
41
- ``origami/``
42
- * Core scripts used to parse a PDF file. All objects and features are
43
- provided here.
38
+ ``tests/``
39
+ Test case units.
44
40
 
45
- ``samples/``
46
- * Many samples, mostly sorted to generate specially crafted PDFs.
41
+ ``doc/``
42
+ Automated RubyDoc HTML documentation.
47
43
 
48
- ``bin/``
49
- * Useful tools based on Origami.
44
+ HOMEPAGE
50
45
 
51
- ``tests/``
52
- * Test case units.
46
+ http://aslr.fr/pages/Origami
53
47
 
54
- ``doc/``
55
- * Automated RubyDoc HTML documentation.
48
+ CONTRIBUTORS
56
49
 
50
+ Guillaume Delugré <guillaume (at) security-labs.org> - Author
51
+ Frédéric Raynal <fred (at) security-labs.org> - Contributor
57
52
 
58
- :: CONTRIBUTORS
59
- ===============
53
+ LICENSE
60
54
 
61
- Guillaume Delugré <guillaume@security-labs.org> - Author
62
- Frédéric Raynal <fred@security-labs.org> - Contributor
55
+ This software is distributed under the LGPL license.
56
+ See the COPYING.LESSER file for more details.
63
57
 
58
+ NOTES
64
59
 
65
- :: NOTES
66
- ========
67
-
68
- If you encounter a problem, feel free to report it by mail at
69
- <guillaume [at] security-labs [dot] org>, with a short explanation of
70
- what you did and any necessary PDF documents.
71
-
72
- Thanks.
60
+ It contains many bugs and many incomplete features. If you encounter a
61
+ problem, feel free to report it by mail at <guillaume [at] security-labs
62
+ [dot] org>, with a short explanation of what you did and any necessary PDF
63
+ documents. Thanks.
73
64
 
@@ -36,7 +36,7 @@ module PDFWalker
36
36
  :version => Origami::VERSION,
37
37
  :copyright => "Copyright (C) 2010\nGuillaume Delugre, Sogeti-ESEC R&D <guillaume@security-labs.org>\nAll right reserved.",
38
38
  :comments => "A graphical PDF parser front-end",
39
- :license => File.read("#{File.dirname($0)}/../COPYING.LESSER")
39
+ :license => File.read("#{File.dirname(__FILE__)}/../../COPYING.LESSER")
40
40
  })
41
41
 
42
42
  end
@@ -37,7 +37,7 @@ require 'optparse'
37
37
 
38
38
  class OptParser
39
39
  BANNER = <<USAGE
40
- Usage: #{$0} [<PDF-file>] [-p <password>] [-c <cipher>] [-s <key-size>] [-o <output-file>]
40
+ Usage: #{$0} [<PDF-file>] [-p <password>] [-c <cipher>] [-s <key-size>] [--hardened] [-o <output-file>]
41
41
  Encrypts a PDF document. Supports RC4 40 to 128 bits, AES128, AES256.
42
42
  Bug reports or feature requests at: http://origami-pdf.googlecode.com/
43
43
 
@@ -64,6 +64,10 @@ USAGE
64
64
  options[:key_size] = s.to_i
65
65
  end
66
66
 
67
+ opts.on("--hardened", "Use stronger key validation scheme (only AES-256)") do
68
+ options[:hardened] = true
69
+ end
70
+
67
71
  opts.on_tail("-h", "--help", "Show this message") do
68
72
  puts opts
69
73
  exit
@@ -77,7 +81,8 @@ USAGE
77
81
  :output => STDOUT,
78
82
  :password => '',
79
83
  :cipher => 'aes',
80
- :key_size => 128
84
+ :key_size => 128,
85
+ :hardened => false
81
86
  }
82
87
 
83
88
  self.parser(options).parse!(args)
@@ -100,13 +105,14 @@ begin
100
105
  :user_password => @options[:password],
101
106
  :owner_password => @options[:password],
102
107
  :cipher => @options[:cipher],
103
- :key_size => @options[:key_size]
108
+ :key_size => @options[:key_size],
109
+ :hardened => @options[:hardened]
104
110
  )
105
111
  pdf.save(@options[:output], :noindent => true)
106
112
 
107
113
  rescue SystemExit
108
114
  rescue Exception => e
109
- STDERR.puts "#{e.class}: #{e.message}"
115
+ STDERR.puts "#{e.class}: #{e.message} #{e.backtrace}"
110
116
  exit 1
111
117
  end
112
118
 
@@ -112,7 +112,6 @@ module Origami
112
112
  def initialize(script)
113
113
  super(:JS => script)
114
114
  end
115
-
116
115
  end
117
116
 
118
117
  #
@@ -0,0 +1,96 @@
1
+ =begin
2
+
3
+ = File
4
+ docmdp.rb
5
+
6
+ = Info
7
+ This file is part of Origami, PDF manipulation framework for Ruby
8
+ Copyright (C) 2010 Guillaume Delugr� <guillaume@security-labs.org>
9
+ All right reserved.
10
+
11
+ Origami is free software: you can redistribute it and/or modify
12
+ it under the terms of the GNU Lesser General Public License as published by
13
+ the Free Software Foundation, either version 3 of the License, or
14
+ (at your option) any later version.
15
+
16
+ Origami is distributed in the hope that it will be useful,
17
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ GNU Lesser General Public License for more details.
20
+
21
+ You should have received a copy of the GNU Lesser General Public License
22
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
23
+
24
+ =end
25
+
26
+ module Origami
27
+
28
+ class Null
29
+ def to_docmdp_str
30
+ "\000"
31
+ end
32
+ end
33
+
34
+ class Integer
35
+ def to_docmdp_str
36
+ [ 1, self.value & 0xFFFFFFFF ].pack("CN")
37
+ end
38
+ end
39
+
40
+ class Real
41
+ def to_docmdp_str
42
+ [ 2, self.value.round & 0xFFFFFFFF ].pack("CN")
43
+ end
44
+ end
45
+
46
+ class Boolean
47
+ def to_docmdp_str
48
+ [ 3, (self.false?) ? 0 : 1 ].pack("CN")
49
+ end
50
+ end
51
+
52
+ class Name
53
+ def to_docmdp_str
54
+ [ 4, self.to_s.length, self.to_s ].pack("CNA*")
55
+ end
56
+ end
57
+
58
+ class String
59
+ def to_docmdp_str
60
+ [ 5, self.to_s.length, self.to_s ].pack("CNA*")
61
+ end
62
+ end
63
+
64
+ class Dictionary
65
+ def to_docmdp_str(*fields)
66
+ if fields.empty?
67
+ self.each_pair { |key, value|
68
+
69
+ }
70
+ else
71
+
72
+ end
73
+ end
74
+ end
75
+
76
+ class Array
77
+ def to_docmdp_str
78
+ str = [ 7, self.length ].pack("CN")
79
+
80
+ self.each do |obj|
81
+ str << obj.to_docmdp_str
82
+ end
83
+
84
+ str
85
+ end
86
+ end
87
+
88
+ class Stream
89
+ def to_docmdp_str
90
+ [ 8, self.dictionary.size ].pack("CN") +
91
+ self.dictionary.to_docmdp_str(:DecodeParms, :F, :FDecodeParms, :FFilter, :Filter, :Length) +
92
+ [ self.rawdata.length, self.rawdata ].pack("NA*")
93
+ end
94
+ end
95
+
96
+ end
@@ -49,7 +49,6 @@ module Origami
49
49
 
50
50
  #
51
51
  # Decrypts the current document (only RC4 40..128 bits).
52
- # TODO: AESv3
53
52
  # _passwd_:: The password to decrypt the document.
54
53
  #
55
54
  def decrypt(passwd = "")
@@ -209,6 +208,7 @@ module Origami
209
208
  :owner_passwd => '',
210
209
  :cipher => 'rc4', # :RC4 or :AES
211
210
  :key_size => 128, # Key size in bits
211
+ :hardened => false, # Use newer password validation (since Reader X)
212
212
  :encrypt_metadata => true, # Metadata shall be encrypted?
213
213
  :permissions => Encryption::Standard::Permissions::ALL # Document permissions
214
214
  }.update(options)
@@ -234,7 +234,12 @@ module Origami
234
234
  if params[:key_size] == 128
235
235
  version = revision = 4
236
236
  elsif params[:key_size] == 256
237
- version = revision = 5
237
+ version = 5
238
+ if params[:hardened]
239
+ revision = 6
240
+ else
241
+ revision = 5
242
+ end
238
243
  else
239
244
  raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
240
245
  end
@@ -1069,6 +1074,14 @@ module Origami
1069
1074
  field :Perms, :Type => String, :Version => '1.7', :ExtensionLevel => 3
1070
1075
  field :P, :Type => Integer, :Default => 0, :Required => true
1071
1076
  field :EncryptMetadata, :Type => Boolean, :Default => true, :Version => "1.5"
1077
+
1078
+ def pdf_version_required #:nodoc:
1079
+ if self.R > 5
1080
+ [ 1.7, 8 ]
1081
+ else
1082
+ super
1083
+ end
1084
+ end
1072
1085
 
1073
1086
  #
1074
1087
  # Computes the key that will be used to encrypt/decrypt the document contents with user password.
@@ -1099,7 +1112,12 @@ module Origami
1099
1112
  passwd = password_to_utf8(userpassword)
1100
1113
 
1101
1114
  uks = self.U[40, 8]
1102
- ukey = Digest::SHA256.digest(passwd + uks)
1115
+
1116
+ if self.R == 5
1117
+ ukey = Digest::SHA256.digest(passwd + uks)
1118
+ else
1119
+ ukey = compute_hardened_hash(passwd, uks)
1120
+ end
1103
1121
 
1104
1122
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1105
1123
  AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
@@ -1108,15 +1126,20 @@ module Origami
1108
1126
 
1109
1127
  #
1110
1128
  # Computes the key that will be used to encrypt/decrypt the document contents with owner password.
1111
- # Revision 5 only.
1129
+ # Revision 5 and above.
1112
1130
  #
1113
1131
  def compute_owner_encryption_key(ownerpassword)
1114
- if self.R == 5
1132
+ if self.R >= 5
1115
1133
  passwd = password_to_utf8(ownerpassword)
1116
1134
 
1117
1135
  oks = self.O[40, 8]
1118
- okey = Digest::SHA256.digest(passwd + oks)
1119
-
1136
+
1137
+ if self.R == 5
1138
+ okey = Digest::SHA256.digest(passwd + oks)
1139
+ else
1140
+ okey = compute_hardened_hash(passwd, oks)
1141
+ end
1142
+
1120
1143
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1121
1144
  AES.new(okey, nil, false).decrypt(iv + self.OE.value)
1122
1145
  end
@@ -1136,21 +1159,32 @@ module Origami
1136
1159
  self.O = owner_key
1137
1160
  self.U = compute_user_password(userpassword, salt)
1138
1161
 
1139
- elsif self.R == 5
1162
+ else
1140
1163
  upass = password_to_utf8(userpassword)
1141
1164
  opass = password_to_utf8(ownerpassword)
1142
1165
 
1143
1166
  uvs, uks, ovs, oks = ::Array.new(4) { ::Array.new(8) { rand(255) }.pack("C*") }
1144
1167
  file_key = ::Array.new(32) { rand(256) }.pack("C*")
1145
1168
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
1146
-
1147
- ukey = Digest::SHA256.digest(upass + uks)
1148
- okey = Digest::SHA256.digest(opass + oks)
1149
1169
 
1170
+ if self.R == 5
1171
+ ukey = Digest::SHA256.digest(upass + uks)
1172
+ okey = Digest::SHA256.digest(opass + oks)
1173
+ else
1174
+ ukey = compute_hardened_hash(upass, uks)
1175
+ okey = compute_hardened_hash(upass, oks)
1176
+ end
1177
+
1150
1178
  self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
1151
1179
  self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
1152
- self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
1153
- self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
1180
+
1181
+ if self.R == 5
1182
+ self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
1183
+ self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
1184
+ else
1185
+ self.U = compute_hardened_hash(upass, uvs) + uvs + uks
1186
+ self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
1187
+ end
1154
1188
 
1155
1189
  perms =
1156
1190
  [ self.P ].pack("V") + # 0-3
@@ -1168,7 +1202,7 @@ module Origami
1168
1202
  #
1169
1203
  # Checks user password.
1170
1204
  # For version 2,3 and 4, _salt_ is the document ID.
1171
- # For version 5, _salt_ is the User Key Salt.
1205
+ # For version 5 and 6, _salt_ is the User Key Salt.
1172
1206
  #
1173
1207
  def is_user_password?(pass, salt)
1174
1208
 
@@ -1179,6 +1213,9 @@ module Origami
1179
1213
  elsif self.R == 5
1180
1214
  uvs = self.U[32, 8]
1181
1215
  Digest::SHA256.digest(pass + uvs) == self.U[0, 32]
1216
+ elsif self.R == 6
1217
+ uvs = self.U[32, 8]
1218
+ compute_hardened_hash(pass, uvs) == self.U[0, 32]
1182
1219
  end
1183
1220
  end
1184
1221
 
@@ -1195,6 +1232,9 @@ module Origami
1195
1232
  elsif self.R == 5
1196
1233
  ovs = self.O[32, 8]
1197
1234
  Digest::SHA256.digest(pass + ovs + self.U) == self.O[0, 32]
1235
+ elsif self.R == 6
1236
+ ovs = self.O[32, 8]
1237
+ compute_hardened_hash(pass, ovs, self.U[0,48]) == self.O[0, 32]
1198
1238
  end
1199
1239
  end
1200
1240
 
@@ -1258,6 +1298,55 @@ module Origami
1258
1298
  user_key.ljust(32, "\xFF")
1259
1299
  end
1260
1300
  end
1301
+
1302
+ #
1303
+ # Computes hardened hash used in revision 6 (extension level 8).
1304
+ #
1305
+ def compute_hardened_hash(password, salt, vector = '')
1306
+ block_size = 32
1307
+ input = Digest::SHA256.digest(password + salt + vector) + "\x00" * 32
1308
+ key = input[0, 16]
1309
+ iv = input[16, 16]
1310
+ digest, aes, h, x = nil, nil, nil, nil
1311
+
1312
+ i = 0
1313
+ while i < 64 or i < x[-1].ord + 32
1314
+ j = 0
1315
+ block = input[0, block_size]
1316
+
1317
+ if Origami::OPTIONS[:use_openssl]
1318
+ aes = OpenSSL::Cipher::Cipher.new("aes-128-cbc").encrypt
1319
+ aes.iv = iv
1320
+ aes.key = key
1321
+ aes.padding = 0
1322
+ else
1323
+ fail "You need OpenSSL support to encrypt/decrypt documents with this method"
1324
+ end
1325
+
1326
+ 64.times do |j|
1327
+ x = aes.update(block)
1328
+ unless vector.empty?
1329
+ x += aes.update(vector)
1330
+ end
1331
+
1332
+ if j == 0
1333
+ block_size = 32 + (x.unpack("C16").inject(0) {|a,b| a+b} % 3) * 16
1334
+ digest = Digest::SHA2.new(block_size << 3)
1335
+ end
1336
+
1337
+ digest.update(x)
1338
+ end
1339
+
1340
+ h = digest.digest
1341
+ key = h[0, 16]
1342
+ input[0, block_size] = h[0, block_size]
1343
+ iv = h[16, 16]
1344
+
1345
+ i = i + 1
1346
+ end
1347
+
1348
+ h[0, 32]
1349
+ end
1261
1350
 
1262
1351
  def xor(str, byte) #:nodoc:
1263
1352
  str.split(//).map!{|c| (c[0].ord ^ byte).chr }.join