origami 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +1 -1
- data/bin/gui/hexview.rb +1 -1
- data/bin/gui/menu.rb +4 -4
- data/bin/gui/textview.rb +6 -4
- data/bin/gui/treeview.rb +4 -4
- data/bin/gui/walker.rb +1 -1
- data/bin/pdf2graph +1 -1
- data/bin/pdf2pdfa +1 -1
- data/bin/pdf2ruby +1 -1
- data/bin/pdfcocoon +1 -1
- data/bin/pdfcop +1 -1
- data/bin/pdfdecompress +1 -1
- data/bin/pdfdecrypt +1 -1
- data/bin/pdfencrypt +1 -1
- data/bin/pdfextract +75 -14
- data/bin/pdfmetadata +1 -1
- data/bin/shell/.irbrc +1 -1
- data/{origami.rb → lib/origami.rb} +3 -3
- data/{origami → lib/origami}/3d.rb +0 -0
- data/{origami → lib/origami}/acroform.rb +2 -2
- data/{origami → lib/origami}/actions.rb +0 -0
- data/{origami → lib/origami}/annotations.rb +0 -0
- data/{origami → lib/origami}/array.rb +0 -0
- data/{origami → lib/origami}/boolean.rb +0 -0
- data/{origami → lib/origami}/catalog.rb +0 -0
- data/{origami → lib/origami}/destinations.rb +0 -0
- data/{origami → lib/origami}/dictionary.rb +0 -0
- data/{origami → lib/origami}/docmdp.rb +0 -0
- data/{origami → lib/origami}/encryption.rb +9 -7
- data/{origami → lib/origami}/export.rb +0 -0
- data/lib/origami/extensions/fdf.rb +257 -0
- data/{origami/adobe → lib/origami/extensions}/ppklite.rb +3 -1
- data/{origami → lib/origami}/file.rb +0 -0
- data/{origami → lib/origami}/filters.rb +0 -0
- data/{origami → lib/origami}/filters/ascii.rb +0 -0
- data/{origami → lib/origami}/filters/ccitt.rb +0 -1
- data/{origami → lib/origami}/filters/crypt.rb +0 -0
- data/{origami → lib/origami}/filters/dct.rb +0 -0
- data/{origami → lib/origami}/filters/flate.rb +0 -0
- data/{origami → lib/origami}/filters/jbig2.rb +0 -0
- data/{origami → lib/origami}/filters/jpx.rb +0 -0
- data/{origami → lib/origami}/filters/lzw.rb +0 -0
- data/{origami → lib/origami}/filters/predictors.rb +0 -0
- data/{origami → lib/origami}/filters/runlength.rb +0 -0
- data/{origami → lib/origami}/font.rb +0 -0
- data/{origami → lib/origami}/functions.rb +0 -0
- data/{origami → lib/origami}/graphics.rb +0 -0
- data/{origami → lib/origami}/graphics/colors.rb +45 -23
- data/{origami → lib/origami}/graphics/instruction.rb +0 -0
- data/{origami → lib/origami}/graphics/path.rb +0 -0
- data/{origami → lib/origami}/graphics/patterns.rb +0 -0
- data/{origami → lib/origami}/graphics/render.rb +0 -0
- data/{origami → lib/origami}/graphics/state.rb +2 -2
- data/{origami → lib/origami}/graphics/text.rb +0 -0
- data/{origami → lib/origami}/graphics/xobject.rb +219 -0
- data/{origami → lib/origami}/header.rb +0 -0
- data/{origami → lib/origami}/javascript.rb +0 -0
- data/{origami → lib/origami}/linearization.rb +0 -0
- data/{origami → lib/origami}/metadata.rb +0 -0
- data/{origami → lib/origami}/name.rb +0 -0
- data/{origami → lib/origami}/null.rb +0 -0
- data/{origami → lib/origami}/numeric.rb +0 -0
- data/{origami → lib/origami}/obfuscation.rb +0 -0
- data/{origami → lib/origami}/object.rb +7 -2
- data/{origami → lib/origami}/outline.rb +0 -0
- data/{origami → lib/origami}/outputintents.rb +0 -0
- data/{origami → lib/origami}/page.rb +0 -0
- data/{origami → lib/origami}/parser.rb +76 -51
- data/{origami → lib/origami}/parsers/fdf.rb +9 -6
- data/{origami/parsers/pdf/linear.rb → lib/origami/parsers/pdf.rb} +31 -39
- data/lib/origami/parsers/pdf/linear.rb +84 -0
- data/lib/origami/parsers/ppklite.rb +93 -0
- data/{origami → lib/origami}/pdf.rb +6 -3
- data/{origami → lib/origami}/reference.rb +0 -0
- data/{origami → lib/origami}/signature.rb +170 -19
- data/{origami → lib/origami}/stream.rb +9 -0
- data/{origami → lib/origami}/string.rb +0 -0
- data/{origami → lib/origami}/trailer.rb +0 -0
- data/{origami → lib/origami}/webcapture.rb +0 -0
- data/{origami → lib/origami}/xfa.rb +0 -0
- data/{origami → lib/origami}/xreftable.rb +3 -7
- data/samples/README.txt +45 -0
- data/samples/actions/launch/calc.rb +87 -0
- data/samples/actions/launch/winparams.rb +22 -0
- data/samples/actions/loop/loopgoto.rb +24 -0
- data/samples/actions/loop/loopnamed.rb +21 -0
- data/samples/actions/named/named.rb +31 -0
- data/samples/actions/samba/smbrelay.rb +26 -0
- data/samples/actions/triggerevents/trigger.rb +75 -0
- data/samples/actions/webbug/submitform.js +26 -0
- data/samples/actions/webbug/webbug-browser.rb +68 -0
- data/samples/actions/webbug/webbug-js.rb +67 -0
- data/samples/actions/webbug/webbug-reader.rb +90 -0
- data/samples/attachments/attach.rb +40 -0
- data/samples/attachments/attached.txt +1 -0
- data/samples/crypto/crypto.rb +28 -0
- data/samples/digsig/signed.rb +46 -0
- data/samples/exploits/cve-2008-2992-utilprintf.rb +87 -0
- data/samples/exploits/cve-2009-0927-geticon.rb +65 -0
- data/samples/exploits/exploit_customdictopen.rb +55 -0
- data/samples/exploits/getannots.rb +69 -0
- data/samples/flash/flash.rb +31 -0
- data/samples/flash/helloworld.swf +0 -0
- data/samples/javascript/attached.txt +1 -0
- data/samples/javascript/js.rb +52 -0
- data/{tests → test}/ts_pdf.rb +1 -1
- metadata +109 -95
- data/origami/adobe/fdf.rb +0 -259
- data/origami/parsers/pdf.rb +0 -27
- data/origami/parsers/ppklite.rb +0 -86
- data/tests/dataset/test.dummycrt +0 -28
- data/tests/dataset/test.dummykey +0 -27
- data/tests/tc_actions.rb +0 -32
- data/tests/tc_annotations.rb +0 -85
- data/tests/tc_pages.rb +0 -37
- data/tests/tc_pdfattach.rb +0 -24
- data/tests/tc_pdfencrypt.rb +0 -110
- data/tests/tc_pdfnew.rb +0 -32
- data/tests/tc_pdfparse.rb +0 -98
- data/tests/tc_pdfsig.rb +0 -37
- data/tests/tc_streams.rb +0 -129
@@ -20,11 +20,10 @@
|
|
20
20
|
=end
|
21
21
|
|
22
22
|
require 'origami/parser'
|
23
|
-
require 'origami/adobe/fdf'
|
24
23
|
|
25
24
|
module Origami
|
26
25
|
|
27
|
-
class
|
26
|
+
class FDF
|
28
27
|
class Parser < Origami::Parser
|
29
28
|
def parse(stream) #:nodoc:
|
30
29
|
super
|
@@ -33,11 +32,15 @@ module Origami
|
|
33
32
|
fdf.header = Adobe::FDF::Header.parse(stream)
|
34
33
|
@options[:callback].call(fdf.header)
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
loop do
|
36
|
+
break if (object = parse_object).nil?
|
37
|
+
fdf << object
|
38
|
+
end
|
39
|
+
|
40
|
+
fdf.revisions.first.xreftable = parse_xreftable
|
41
|
+
fdf.revisions.first.trailer = parse_trailer
|
39
42
|
|
40
|
-
|
43
|
+
fdf
|
41
44
|
end
|
42
45
|
end
|
43
46
|
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
=begin
|
2
2
|
|
3
3
|
= File
|
4
|
-
parsers/
|
4
|
+
parsers/pdf.rb
|
5
5
|
|
6
6
|
= Info
|
7
|
-
|
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
|
8
12
|
it under the terms of the GNU Lesser General Public License as published by
|
9
13
|
the Free Software Foundation, either version 3 of the License, or
|
10
14
|
(at your option) any later version.
|
@@ -19,21 +23,29 @@
|
|
19
23
|
|
20
24
|
=end
|
21
25
|
|
22
|
-
|
23
26
|
require 'origami/parser'
|
24
|
-
require 'origami/pdf'
|
25
27
|
|
26
28
|
module Origami
|
27
|
-
|
29
|
+
|
28
30
|
class PDF
|
31
|
+
class Parser < Origami::Parser
|
32
|
+
def initialize(params = {})
|
33
|
+
options =
|
34
|
+
{
|
35
|
+
:password => '', # Default password being tried when opening a protected document.
|
36
|
+
:prompt_password => Proc.new {
|
37
|
+
print "Password: "
|
38
|
+
gets.chomp
|
39
|
+
}, # Callback procedure to prompt password when document is encrypted.
|
40
|
+
:force => false # Force PDF header detection
|
41
|
+
}.update(params)
|
42
|
+
|
43
|
+
super(options)
|
44
|
+
end
|
29
45
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
class LinearParser < Origami::Parser
|
34
|
-
def parse(stream)
|
35
|
-
super
|
36
|
-
|
46
|
+
private
|
47
|
+
|
48
|
+
def parse_initialize #:nodoc:
|
37
49
|
if @options[:force] == true
|
38
50
|
@data.skip_until(/%PDF-/).nil?
|
39
51
|
@data.pos = @data.pos - 5
|
@@ -52,34 +64,11 @@ module Origami
|
|
52
64
|
raise e
|
53
65
|
end
|
54
66
|
end
|
55
|
-
|
56
|
-
#
|
57
|
-
# Parse each revision
|
58
|
-
#
|
59
|
-
revision = 0
|
60
|
-
until @data.eos? do
|
61
|
-
|
62
|
-
begin
|
63
|
-
|
64
|
-
pdf.add_new_revision unless revision.zero?
|
65
|
-
revision = revision.succ
|
66
|
-
|
67
|
-
info "...Parsing revision #{pdf.revisions.size}..."
|
68
|
-
parse_objects(pdf)
|
69
|
-
parse_xreftable(pdf)
|
70
|
-
parse_trailer(pdf)
|
71
|
-
|
72
|
-
rescue SystemExit
|
73
|
-
raise
|
74
|
-
rescue Exception => e
|
75
|
-
error "Cannot read : " + (@data.peek(10) + "...").inspect
|
76
|
-
error "Stopped on exception : " + e.message
|
77
|
-
|
78
|
-
break
|
79
|
-
end
|
80
|
-
|
81
|
-
end
|
82
67
|
|
68
|
+
pdf
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_finalize(pdf) #:nodoc:
|
83
72
|
warn "This file has been linearized." if pdf.is_linearized?
|
84
73
|
|
85
74
|
#
|
@@ -109,5 +98,8 @@ module Origami
|
|
109
98
|
end
|
110
99
|
end
|
111
100
|
end
|
101
|
+
|
112
102
|
end
|
113
103
|
|
104
|
+
require 'origami/parsers/pdf/linear'
|
105
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
parsers/linear.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
Origami is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU Lesser General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Origami is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU Lesser General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU Lesser General Public License
|
18
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
=end
|
21
|
+
|
22
|
+
|
23
|
+
require 'origami/parser'
|
24
|
+
require 'origami/pdf'
|
25
|
+
|
26
|
+
module Origami
|
27
|
+
|
28
|
+
class PDF
|
29
|
+
|
30
|
+
#
|
31
|
+
# Create a new PDF linear Parser.
|
32
|
+
#
|
33
|
+
class LinearParser < Parser
|
34
|
+
def parse(stream)
|
35
|
+
super
|
36
|
+
|
37
|
+
pdf = parse_initialize
|
38
|
+
|
39
|
+
#
|
40
|
+
# Parse each revision
|
41
|
+
#
|
42
|
+
revision = 0
|
43
|
+
until @data.eos? do
|
44
|
+
|
45
|
+
begin
|
46
|
+
pdf.add_new_revision unless revision.zero?
|
47
|
+
revision = revision + 1
|
48
|
+
|
49
|
+
info "...Parsing revision #{pdf.revisions.size}..."
|
50
|
+
loop do
|
51
|
+
break if (object = parse_object).nil?
|
52
|
+
pdf.insert(object)
|
53
|
+
end
|
54
|
+
|
55
|
+
pdf.revisions.last.xreftable = parse_xreftable
|
56
|
+
|
57
|
+
trailer = parse_trailer
|
58
|
+
pdf.revisions.last.trailer = trailer
|
59
|
+
|
60
|
+
xrefstm = pdf.get_object_by_offset(trailer.startxref) ||
|
61
|
+
(pdf.get_object_by_offset(trailer.XRefStm) if trailer.has_field? :XRefStm)
|
62
|
+
|
63
|
+
if not xrefstm.nil?
|
64
|
+
debug "Found a XRefStream for this revision at #{xrefstm.reference}"
|
65
|
+
pdf.revisions.last.xrefstm = xrefstm
|
66
|
+
end
|
67
|
+
|
68
|
+
rescue SystemExit
|
69
|
+
raise
|
70
|
+
rescue Exception => e
|
71
|
+
error "Cannot read : " + (@data.peek(10) + "...").inspect
|
72
|
+
error "Stopped on exception : " + e.message
|
73
|
+
|
74
|
+
break
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
parse_finalize(pdf)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
parsers/ppklite.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
Origami is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the GNU Lesser General Public License as published by
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
Origami is distributed in the hope that it will be useful,
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
GNU Lesser General Public License for more details.
|
16
|
+
|
17
|
+
You should have received a copy of the GNU Lesser General Public License
|
18
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
=end
|
21
|
+
|
22
|
+
require 'origami/parser'
|
23
|
+
|
24
|
+
module Origami
|
25
|
+
|
26
|
+
module Adobe
|
27
|
+
|
28
|
+
class PPKLite
|
29
|
+
|
30
|
+
class Parser < Origami::Parser
|
31
|
+
def parse(stream) #:nodoc:
|
32
|
+
super
|
33
|
+
|
34
|
+
addrbk = Adobe::PPKLite.new
|
35
|
+
addrbk.header = Adobe::PPKLite::Header.parse(stream)
|
36
|
+
@options[:callback].call(addrbk.header)
|
37
|
+
|
38
|
+
loop do
|
39
|
+
break if (object = parse_object).nil?
|
40
|
+
addrbk << object
|
41
|
+
end
|
42
|
+
|
43
|
+
addrbk.revisions.first.xreftable = parse_xreftable
|
44
|
+
addrbm.revisions.first.trailer = parse_trailer
|
45
|
+
book_specialize_entries(addrbk)
|
46
|
+
|
47
|
+
addrbk
|
48
|
+
end
|
49
|
+
|
50
|
+
def book_specialize_entries(addrbk) #:nodoc:
|
51
|
+
addrbk.revisions.first.body.each_pair do |ref, obj|
|
52
|
+
|
53
|
+
if obj.is_a?(Dictionary)
|
54
|
+
|
55
|
+
if obj[:Type] == :Catalog
|
56
|
+
|
57
|
+
o = Adobe::PPKLite::Catalog.new(obj)
|
58
|
+
o.generation, o.no, o.file_offset = obj.generation, obj.no, obj.file_offset
|
59
|
+
|
60
|
+
if o.PPK.is_a?(Dictionary) and o.PPK[:Type] == :PPK
|
61
|
+
o.PPK = Adobe::PPKLite::PPK.new(o.PPK)
|
62
|
+
|
63
|
+
if o.PPK.User.is_a?(Dictionary) and o.PPK.User[:Type] == :User
|
64
|
+
o.PPK.User = Adobe::PPKLite::UserList.new(o.PPK.User)
|
65
|
+
end
|
66
|
+
|
67
|
+
if o.PPK.AddressBook.is_a?(Dictionary) and o.PPK.AddressBook[:Type] == :AddressBook
|
68
|
+
o.PPK.AddressBook = Adobe::PPKLite::AddressList.new(o.PPK.AddressBook)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
addrbk.revisions.first.body[ref] = o
|
73
|
+
|
74
|
+
elsif obj[:ABEType] == Adobe::PPKLite::Descriptor::USER
|
75
|
+
o = Adobe::PPKLite::User.new(obj)
|
76
|
+
o.generation, o.no, o.file_offset = obj.generation, obj.no, obj.file_offset
|
77
|
+
|
78
|
+
addrbk.revisions.first.body[ref] = o
|
79
|
+
elsif obj[:ABEType] == Adobe::PPKLite::Descriptor::CERTIFICATE
|
80
|
+
o = Adobe::PPKLite::Certificate.new(obj)
|
81
|
+
o.generation, o.no, o.file_offset = obj.generation, obj.no, obj.file_offset
|
82
|
+
|
83
|
+
addrbk.revisions.first.body[ref] = o
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
@@ -57,10 +57,12 @@ require 'origami/xfa'
|
|
57
57
|
require 'origami/javascript'
|
58
58
|
require 'origami/outputintents'
|
59
59
|
|
60
|
+
require 'origami/parsers/pdf'
|
61
|
+
|
60
62
|
module Origami
|
61
63
|
|
62
|
-
VERSION = "1.2.
|
63
|
-
REVISION = "$Revision: rev
|
64
|
+
VERSION = "1.2.2"
|
65
|
+
REVISION = "$Revision: rev 135/, 2011/10/17 11:59:41 $" #:nodoc:
|
64
66
|
|
65
67
|
#
|
66
68
|
# Global options for Origami.
|
@@ -181,7 +183,7 @@ module Origami
|
|
181
183
|
#
|
182
184
|
# Reads and parses a PDF file from disk.
|
183
185
|
#
|
184
|
-
def read(filename, options = {
|
186
|
+
def read(filename, options = {})
|
185
187
|
filename = File.expand_path(filename) if filename.is_a?(::String)
|
186
188
|
PDF::LinearParser.new(options).parse(filename)
|
187
189
|
end
|
@@ -293,6 +295,7 @@ module Origami
|
|
293
295
|
if path.respond_to?(:write)
|
294
296
|
fd = path
|
295
297
|
else
|
298
|
+
path = File.expand_path(path)
|
296
299
|
fd = File.open(path, 'w').binmode
|
297
300
|
end
|
298
301
|
|
File without changes
|
@@ -19,9 +19,71 @@
|
|
19
19
|
|
20
20
|
=end
|
21
21
|
|
22
|
+
require 'openssl'
|
23
|
+
require 'digest/sha1'
|
24
|
+
|
22
25
|
module Origami
|
23
26
|
|
24
27
|
class PDF
|
28
|
+
|
29
|
+
class SignatureError < Exception #:nodoc:
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Verify a document signature.
|
34
|
+
# Options:
|
35
|
+
# _:trusted_: an array of trusted X509 certificates.
|
36
|
+
# If no argument is passed, embedded certificates are treated as trusted.
|
37
|
+
#
|
38
|
+
def verify(options = {})
|
39
|
+
params =
|
40
|
+
{
|
41
|
+
:trusted => []
|
42
|
+
}.update(options)
|
43
|
+
|
44
|
+
digsig = self.signature
|
45
|
+
|
46
|
+
unless digsig[:Contents].is_a?(String)
|
47
|
+
raise SignatureError, "Invalid digital signature contents"
|
48
|
+
end
|
49
|
+
|
50
|
+
store = OpenSSL::X509::Store.new
|
51
|
+
params[:trusted].each do |ca| store.add_cert(ca) end
|
52
|
+
flags = 0
|
53
|
+
flags |= OpenSSL::PKCS7::NOVERIFY if params[:trusted].empty?
|
54
|
+
|
55
|
+
stream = StringScanner.new(self.original_data)
|
56
|
+
stream.pos = digsig[:Contents].file_offset
|
57
|
+
Object.typeof(stream).parse(stream)
|
58
|
+
endofsig_offset = stream.pos
|
59
|
+
stream.terminate
|
60
|
+
|
61
|
+
s1,l1,s2,l2 = digsig.ByteRange
|
62
|
+
if s1.value != 0 or
|
63
|
+
(s2.value + l2.value) != self.original_data.size or
|
64
|
+
(s1.value + l1.value) != digsig[:Contents].file_offset or
|
65
|
+
s2.value != endofsig_offset
|
66
|
+
|
67
|
+
raise SignatureError, "Invalid signature byte range"
|
68
|
+
end
|
69
|
+
|
70
|
+
data = self.original_data[s1,l1] + self.original_data[s2,l2]
|
71
|
+
|
72
|
+
case digsig.SubFilter.value.to_s
|
73
|
+
when 'adbe.pkcs7.detached'
|
74
|
+
flags |= OpenSSL::PKCS7::DETACHED
|
75
|
+
p7 = OpenSSL::PKCS7.new(digsig[:Contents].value)
|
76
|
+
raise SignatureError, "Not a PKCS7 detached signature" unless p7.detached?
|
77
|
+
p7.verify([], store, data, flags)
|
78
|
+
|
79
|
+
when 'adbe.pkcs7.sha1'
|
80
|
+
p7 = OpenSSL::PKCS7.new(digsig[:Contents].value)
|
81
|
+
p7.verify([], store, nil, flags) and p7.data == Digest::SHA1.digest(data)
|
82
|
+
|
83
|
+
else
|
84
|
+
raise NotImplementedError, "Unsupported method #{digsig.SubFilter}"
|
85
|
+
end
|
86
|
+
end
|
25
87
|
|
26
88
|
#
|
27
89
|
# Sign the document with the given key and x509 certificate.
|
@@ -29,11 +91,21 @@ module Origami
|
|
29
91
|
# _key_:: The private key associated with the certificate.
|
30
92
|
# _ca_:: Optional CA certificates used to sign the user certificate.
|
31
93
|
#
|
32
|
-
def sign(certificate, key,
|
94
|
+
def sign(certificate, key, options = {})
|
33
95
|
|
34
96
|
unless Origami::OPTIONS[:use_openssl]
|
35
97
|
fail "OpenSSL is not present or has been disabled."
|
36
98
|
end
|
99
|
+
|
100
|
+
params =
|
101
|
+
{
|
102
|
+
:method => "adbe.pkcs7.detached",
|
103
|
+
:ca => [],
|
104
|
+
:annotation => nil,
|
105
|
+
:location => nil,
|
106
|
+
:contact => nil,
|
107
|
+
:reason => nil
|
108
|
+
}.update(options)
|
37
109
|
|
38
110
|
unless certificate.is_a?(OpenSSL::X509::Certificate)
|
39
111
|
raise TypeError, "A OpenSSL::X509::Certificate object must be passed."
|
@@ -43,19 +115,53 @@ module Origami
|
|
43
115
|
raise TypeError, "A OpenSSL::PKey::RSA object must be passed."
|
44
116
|
end
|
45
117
|
|
118
|
+
ca = params[:ca]
|
46
119
|
unless ca.is_a?(::Array)
|
47
120
|
raise TypeError, "Expected an Array of CA certificate."
|
48
121
|
end
|
49
122
|
|
123
|
+
annotation = params[:annotation]
|
50
124
|
unless annotation.nil? or annotation.is_a?(Annotation::Widget::Signature)
|
51
125
|
raise TypeError, "Expected a Annotation::Widget::Signature object."
|
52
126
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
127
|
+
|
128
|
+
case params[:method]
|
129
|
+
when 'adbe.pkcs7.detached'
|
130
|
+
signfield_size = lambda{|crt,key,ca|
|
131
|
+
datatest = "abcdefghijklmnopqrstuvwxyz"
|
132
|
+
OpenSSL::PKCS7.sign(
|
133
|
+
crt,
|
134
|
+
key,
|
135
|
+
datatest,
|
136
|
+
ca,
|
137
|
+
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY
|
138
|
+
).to_der.size + 128
|
139
|
+
}
|
140
|
+
when 'adbe.pkcs7.sha1'
|
141
|
+
signfield_size = lambda{|crt,key,ca|
|
142
|
+
datatest = "abcdefghijklmnopqrstuvwxyz"
|
143
|
+
OpenSSL::PKCS7.sign(
|
144
|
+
crt,
|
145
|
+
key,
|
146
|
+
Digest::SHA1.digest(datatest),
|
147
|
+
ca,
|
148
|
+
OpenSSL::PKCS7::BINARY
|
149
|
+
).to_der.size + 128
|
150
|
+
}
|
151
|
+
|
152
|
+
when 'adbe.x509.rsa_sha1'
|
153
|
+
signfield_size = lambda{|crt,key,ca|
|
154
|
+
datatest = "abcdefghijklmnopqrstuvwxyz"
|
155
|
+
key.private_encrypt(
|
156
|
+
Digest::SHA1.digest(datatest)
|
157
|
+
).size + 128
|
158
|
+
}
|
159
|
+
raise NotImplementedError, "Unsupported method #{params[:method].inspect}"
|
160
|
+
|
161
|
+
else
|
162
|
+
raise NotImplementedError, "Unsupported method #{params[:method].inspect}"
|
57
163
|
end
|
58
|
-
|
164
|
+
|
59
165
|
digsig = Signature::DigitalSignature.new.set_indirect(true)
|
60
166
|
|
61
167
|
if annotation.nil?
|
@@ -63,20 +169,30 @@ module Origami
|
|
63
169
|
annotation.Rect = Rectangle[:llx => 0.0, :lly => 0.0, :urx => 0.0, :ury => 0.0]
|
64
170
|
end
|
65
171
|
|
66
|
-
annotation.V = digsig
|
172
|
+
annotation.V = digsig
|
67
173
|
add_fields(annotation)
|
68
|
-
self.Catalog.AcroForm.SigFlags =
|
174
|
+
self.Catalog.AcroForm.SigFlags =
|
175
|
+
InteractiveForm::SigFlags::SIGNATURESEXIST | InteractiveForm::SigFlags::APPENDONLY
|
69
176
|
|
70
177
|
digsig.Type = :Sig #:nodoc:
|
71
|
-
digsig.Contents = HexaString.new("\x00" * signfield_size
|
178
|
+
digsig.Contents = HexaString.new("\x00" * signfield_size[certificate, key, ca]) #:nodoc:
|
72
179
|
digsig.Filter = Name.new("Adobe.PPKMS") #:nodoc:
|
73
|
-
digsig.SubFilter = Name.new(
|
180
|
+
digsig.SubFilter = Name.new(params[:method]) #:nodoc:
|
74
181
|
digsig.ByteRange = [0, 0, 0, 0] #:nodoc:
|
75
182
|
|
76
|
-
digsig.Location = HexaString.new(location) if location
|
77
|
-
digsig.ContactInfo = HexaString.new(contact) if contact
|
78
|
-
digsig.Reason = HexaString.new(reason) if reason
|
183
|
+
digsig.Location = HexaString.new(params[:location]) if params[:location]
|
184
|
+
digsig.ContactInfo = HexaString.new(params[:contact]) if params[:contact]
|
185
|
+
digsig.Reason = HexaString.new(params[:reason]) if params[:reason]
|
79
186
|
|
187
|
+
if params[:method] == 'adbe.x509.rsa_sha1'
|
188
|
+
digsig.Cert =
|
189
|
+
if ca.empty?
|
190
|
+
HexaString.new(certificate.to_der)
|
191
|
+
else
|
192
|
+
[ HexaString.new(certificate.to_der) ] + ca.map{ |crt| HexaString.new(crt.to_der) }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
80
196
|
#
|
81
197
|
# Flattening the PDF to get file view.
|
82
198
|
#
|
@@ -105,7 +221,30 @@ module Origami
|
|
105
221
|
filedata = self.to_bin
|
106
222
|
signable_data = filedata[digsig.ByteRange[0],digsig.ByteRange[1]] + filedata[digsig.ByteRange[2],digsig.ByteRange[3]]
|
107
223
|
|
108
|
-
signature =
|
224
|
+
signature =
|
225
|
+
case params[:method]
|
226
|
+
when 'adbe.pkcs7.detached'
|
227
|
+
OpenSSL::PKCS7.sign(
|
228
|
+
certificate,
|
229
|
+
key,
|
230
|
+
signable_data,
|
231
|
+
ca,
|
232
|
+
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY
|
233
|
+
).to_der
|
234
|
+
|
235
|
+
when 'adbe.pkcs7.sha1'
|
236
|
+
OpenSSL::PKCS7.sign(
|
237
|
+
certificate,
|
238
|
+
key,
|
239
|
+
Digest::SHA1.digest(signable_data),
|
240
|
+
ca,
|
241
|
+
OpenSSL::PKCS7::BINARY
|
242
|
+
).to_der
|
243
|
+
|
244
|
+
when 'adbe.x509.rsa_sha1'
|
245
|
+
key.private_encrypt(Digest::SHA1.digest(signable_data))
|
246
|
+
end
|
247
|
+
|
109
248
|
digsig.Contents[0, signature.size] = signature
|
110
249
|
|
111
250
|
#
|
@@ -120,7 +259,7 @@ module Origami
|
|
120
259
|
def is_signed?
|
121
260
|
not self.Catalog.AcroForm.nil? and
|
122
261
|
self.Catalog.AcroForm.has_key?(:SigFlags) and
|
123
|
-
(self.Catalog.AcroForm
|
262
|
+
(self.Catalog.AcroForm.SigFlags & InteractiveForm::SigFlags::SIGNATURESEXIST != 0)
|
124
263
|
end
|
125
264
|
|
126
265
|
#
|
@@ -129,10 +268,10 @@ module Origami
|
|
129
268
|
#
|
130
269
|
def enable_usage_rights(cert, pkey, *rights)
|
131
270
|
|
132
|
-
|
271
|
+
signfield_size = lambda{|crt, key, ca|
|
133
272
|
datatest = "abcdefghijklmnopqrstuvwxyz"
|
134
|
-
OpenSSL::PKCS7.sign(
|
135
|
-
|
273
|
+
OpenSSL::PKCS7.sign(crt, key, datatest, ca, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der.size + 128
|
274
|
+
}
|
136
275
|
|
137
276
|
unless Origami::OPTIONS[:use_openssl]
|
138
277
|
fail "OpenSSL is not present or has been disabled."
|
@@ -153,7 +292,7 @@ module Origami
|
|
153
292
|
#self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::APPENDONLY
|
154
293
|
|
155
294
|
digsig.Type = :Sig #:nodoc:
|
156
|
-
digsig.Contents = HexaString.new("\x00" * signfield_size
|
295
|
+
digsig.Contents = HexaString.new("\x00" * signfield_size[certificate, key, []]) #:nodoc:
|
157
296
|
digsig.Filter = Name.new("Adobe.PPKLite") #:nodoc:
|
158
297
|
digsig.Name = "ARE Acrobat Product v8.0 P23 0002337" #:nodoc:
|
159
298
|
digsig.SubFilter = Name.new("adbe.pkcs7.detached") #:nodoc:
|
@@ -221,6 +360,18 @@ module Origami
|
|
221
360
|
not self.Catalog.Perms.nil? and (not self.Catalog.Perms.has_key?(:UR3) or not self.Catalog.Perms.has_key?(:UR))
|
222
361
|
end
|
223
362
|
|
363
|
+
def signature
|
364
|
+
raise SignatureError, "Not a signed document" unless self.is_signed?
|
365
|
+
|
366
|
+
self.each_field do |field|
|
367
|
+
if field.FT == :Sig and field.V.is_a?(Dictionary)
|
368
|
+
return field.V
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
raise SignatureError, "Cannot find digital signature"
|
373
|
+
end
|
374
|
+
|
224
375
|
end
|
225
376
|
|
226
377
|
class Perms < Dictionary
|