origami 1.0.2
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/COPYING.LESSER +165 -0
- data/README +77 -0
- data/VERSION +1 -0
- data/bin/config/pdfcop.conf.yml +237 -0
- data/bin/gui/about.rb +46 -0
- data/bin/gui/config.rb +132 -0
- data/bin/gui/file.rb +385 -0
- data/bin/gui/hexdump.rb +74 -0
- data/bin/gui/hexview.rb +91 -0
- data/bin/gui/imgview.rb +72 -0
- data/bin/gui/menu.rb +392 -0
- data/bin/gui/properties.rb +132 -0
- data/bin/gui/signing.rb +635 -0
- data/bin/gui/textview.rb +107 -0
- data/bin/gui/treeview.rb +409 -0
- data/bin/gui/walker.rb +282 -0
- data/bin/gui/xrefs.rb +79 -0
- data/bin/pdf2graph +121 -0
- data/bin/pdf2ruby +353 -0
- data/bin/pdfcocoon +104 -0
- data/bin/pdfcop +455 -0
- data/bin/pdfdecompress +104 -0
- data/bin/pdfdecrypt +95 -0
- data/bin/pdfencrypt +112 -0
- data/bin/pdfextract +221 -0
- data/bin/pdfmetadata +123 -0
- data/bin/pdfsh +13 -0
- data/bin/pdfwalker +7 -0
- data/bin/shell/.irbrc +104 -0
- data/bin/shell/console.rb +136 -0
- data/bin/shell/hexdump.rb +83 -0
- data/origami.rb +36 -0
- data/origami/3d.rb +239 -0
- data/origami/acroform.rb +321 -0
- data/origami/actions.rb +299 -0
- data/origami/adobe/fdf.rb +259 -0
- data/origami/adobe/ppklite.rb +489 -0
- data/origami/annotations.rb +775 -0
- data/origami/array.rb +187 -0
- data/origami/boolean.rb +101 -0
- data/origami/catalog.rb +486 -0
- data/origami/destinations.rb +213 -0
- data/origami/dictionary.rb +188 -0
- data/origami/docmdp.rb +96 -0
- data/origami/encryption.rb +1293 -0
- data/origami/export.rb +283 -0
- data/origami/file.rb +222 -0
- data/origami/filters.rb +250 -0
- data/origami/filters/ascii.rb +189 -0
- data/origami/filters/ccitt.rb +515 -0
- data/origami/filters/crypt.rb +47 -0
- data/origami/filters/dct.rb +61 -0
- data/origami/filters/flate.rb +112 -0
- data/origami/filters/jbig2.rb +63 -0
- data/origami/filters/jpx.rb +53 -0
- data/origami/filters/lzw.rb +195 -0
- data/origami/filters/predictors.rb +276 -0
- data/origami/filters/runlength.rb +117 -0
- data/origami/font.rb +209 -0
- data/origami/functions.rb +93 -0
- data/origami/graphics.rb +33 -0
- data/origami/graphics/colors.rb +191 -0
- data/origami/graphics/instruction.rb +126 -0
- data/origami/graphics/path.rb +154 -0
- data/origami/graphics/patterns.rb +180 -0
- data/origami/graphics/state.rb +164 -0
- data/origami/graphics/text.rb +224 -0
- data/origami/graphics/xobject.rb +493 -0
- data/origami/header.rb +90 -0
- data/origami/linearization.rb +318 -0
- data/origami/metadata.rb +114 -0
- data/origami/name.rb +170 -0
- data/origami/null.rb +75 -0
- data/origami/numeric.rb +188 -0
- data/origami/obfuscation.rb +233 -0
- data/origami/object.rb +527 -0
- data/origami/outline.rb +59 -0
- data/origami/page.rb +559 -0
- data/origami/parser.rb +268 -0
- data/origami/parsers/fdf.rb +45 -0
- data/origami/parsers/pdf.rb +27 -0
- data/origami/parsers/pdf/linear.rb +113 -0
- data/origami/parsers/ppklite.rb +86 -0
- data/origami/pdf.rb +1144 -0
- data/origami/reference.rb +113 -0
- data/origami/signature.rb +474 -0
- data/origami/stream.rb +575 -0
- data/origami/string.rb +416 -0
- data/origami/trailer.rb +173 -0
- data/origami/webcapture.rb +87 -0
- data/origami/xfa.rb +3027 -0
- data/origami/xreftable.rb +447 -0
- data/templates/patterns.rb +66 -0
- data/templates/widgets.rb +173 -0
- data/templates/xdp.rb +92 -0
- data/tests/dataset/test.dummycrt +28 -0
- data/tests/dataset/test.dummykey +27 -0
- data/tests/tc_actions.rb +32 -0
- data/tests/tc_annotations.rb +85 -0
- data/tests/tc_pages.rb +37 -0
- data/tests/tc_pdfattach.rb +24 -0
- data/tests/tc_pdfencrypt.rb +110 -0
- data/tests/tc_pdfnew.rb +32 -0
- data/tests/tc_pdfparse.rb +98 -0
- data/tests/tc_pdfsig.rb +37 -0
- data/tests/tc_streams.rb +129 -0
- data/tests/ts_pdf.rb +45 -0
- metadata +193 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
properties.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
|
+
require 'iconv'
|
27
|
+
require 'digest/md5'
|
28
|
+
|
29
|
+
module PDFWalker
|
30
|
+
|
31
|
+
class Walker < Window
|
32
|
+
|
33
|
+
def display_file_properties
|
34
|
+
if @opened
|
35
|
+
prop = Properties.new(self, @opened)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Properties < Dialog
|
40
|
+
|
41
|
+
@@acrobat_versions =
|
42
|
+
{
|
43
|
+
1.0 => "1.x",
|
44
|
+
1.1 => "2.x",
|
45
|
+
1.2 => "3.x",
|
46
|
+
1.3 => "4.x",
|
47
|
+
1.4 => "5.x",
|
48
|
+
1.5 => "6.x",
|
49
|
+
1.6 => "7.x",
|
50
|
+
1.7 => "8.x / 9.x / 10.x"
|
51
|
+
}
|
52
|
+
|
53
|
+
def initialize(parent, pdf)
|
54
|
+
super("Document properties", parent, Dialog::MODAL, [Stock::CLOSE, Dialog::RESPONSE_NONE])
|
55
|
+
|
56
|
+
docframe = Frame.new(" File properties ")
|
57
|
+
|
58
|
+
i = Iconv.new("UTF-8//IGNORE//TRANSLIT", "ISO-8859-1")
|
59
|
+
|
60
|
+
stat = File.stat(parent.filename)
|
61
|
+
labels =
|
62
|
+
[
|
63
|
+
[ "Filename:", parent.filename ],
|
64
|
+
[ "File size:", "#{File.size(parent.filename)} bytes" ],
|
65
|
+
[ "MD5:", Digest::MD5.hexdigest(File.open(parent.filename).read) ],
|
66
|
+
[ "Read-only:", "#{not stat.writable?}" ],
|
67
|
+
[ "Creation date:", i.iconv("#{stat.ctime}") ],
|
68
|
+
[ "Last modified:", i.iconv("#{stat.mtime}") ]
|
69
|
+
]
|
70
|
+
i.close
|
71
|
+
|
72
|
+
doctable = Table.new(labels.size + 1, 3)
|
73
|
+
|
74
|
+
row = 0
|
75
|
+
labels.each do |name, value|
|
76
|
+
|
77
|
+
doctable.attach(Label.new(name).set_alignment(1,0), 0, 1, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
78
|
+
doctable.attach(Label.new(value).set_alignment(0,0), 1, 2, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
79
|
+
|
80
|
+
row = row.succ
|
81
|
+
end
|
82
|
+
|
83
|
+
docframe.border_width = 5
|
84
|
+
docframe.shadow_type = Gtk::SHADOW_IN
|
85
|
+
docframe.add(doctable)
|
86
|
+
|
87
|
+
pdfframe = Frame.new(" PDF properties ")
|
88
|
+
|
89
|
+
labels =
|
90
|
+
[
|
91
|
+
[ "Version:", "#{pdf.header.to_f} (Acrobat #{ if pdf.header.to_f >= 1.0 and pdf.header.to_f <= 1.7 then @@acrobat_versions[pdf.header.to_f] else "unknown version" end})" ],
|
92
|
+
[ "Number of revisions:", "#{pdf.revisions.size}" ],
|
93
|
+
[ "Number of indirect objects:", "#{pdf.indirect_objects.size}" ],
|
94
|
+
[ "Number of pages:", "#{pdf.pages.size}" ],
|
95
|
+
[ "Is linearized:", "#{pdf.is_linearized?}" ],
|
96
|
+
[ "Is encrypted:", "#{pdf.is_encrypted?}" ],
|
97
|
+
[ "Is signed:", "#{pdf.is_signed?}" ],
|
98
|
+
[ "Has usage rights:", "#{pdf.has_usage_rights?}"],
|
99
|
+
[ "Contains Acroform:", "#{pdf.has_form?}" ],
|
100
|
+
#[ "Contains XFA forms:", "#{pdf.has_xfa_forms?}" ]
|
101
|
+
[ "Has document information:", "#{pdf.has_document_info?}" ],
|
102
|
+
[ "Has metadata:", "#{pdf.has_metadata?}" ]
|
103
|
+
]
|
104
|
+
|
105
|
+
pdftable = Table.new(labels.size + 1, 3)
|
106
|
+
|
107
|
+
row = 0
|
108
|
+
labels.each do |name, value|
|
109
|
+
|
110
|
+
pdftable.attach(Label.new(name).set_alignment(1,0), 0, 1, row, row + 1, Gtk::FILL, Gtk::SHRINK, 4, 4)
|
111
|
+
pdftable.attach(Label.new(value).set_alignment(0,0), 1, 2, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
112
|
+
|
113
|
+
row = row.succ
|
114
|
+
end
|
115
|
+
|
116
|
+
pdfframe.border_width = 5
|
117
|
+
pdfframe.shadow_type = Gtk::SHADOW_IN
|
118
|
+
pdfframe.add(pdftable)
|
119
|
+
|
120
|
+
vbox.add(docframe)
|
121
|
+
vbox.add(pdfframe)
|
122
|
+
|
123
|
+
signal_connect('response') { destroy }
|
124
|
+
|
125
|
+
show_all
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
data/bin/gui/signing.rb
ADDED
@@ -0,0 +1,635 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
signing.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 PDFWalker
|
27
|
+
|
28
|
+
class Walker < Window
|
29
|
+
|
30
|
+
def display_signing_wizard
|
31
|
+
|
32
|
+
if @opened
|
33
|
+
SignWizard.new(self, @opened)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def display_usage_rights_wizard
|
39
|
+
|
40
|
+
if @opened
|
41
|
+
UsageRightsWizard.new(self, @opened)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
class UsageRightsWizard < Assistant
|
47
|
+
|
48
|
+
def initialize(parent, pdf)
|
49
|
+
|
50
|
+
super()
|
51
|
+
|
52
|
+
@parent = parent
|
53
|
+
|
54
|
+
@pkey, @cert = nil, nil
|
55
|
+
|
56
|
+
create_intro_page
|
57
|
+
create_rights_selection_page
|
58
|
+
create_termination_page
|
59
|
+
|
60
|
+
signal_connect('delete_event') { self.destroy }
|
61
|
+
signal_connect('cancel') { self.destroy }
|
62
|
+
signal_connect('close') { self.destroy }
|
63
|
+
|
64
|
+
signal_connect('apply') {
|
65
|
+
|
66
|
+
rights = []
|
67
|
+
|
68
|
+
rights << UsageRights::Rights::DOCUMENT_FULLSAVE if @document_fullsave.active?
|
69
|
+
|
70
|
+
rights << UsageRights::Rights::ANNOTS_CREATE if @annots_create.active?
|
71
|
+
rights << UsageRights::Rights::ANNOTS_DELETE if @annots_delete.active?
|
72
|
+
rights << UsageRights::Rights::ANNOTS_MODIFY if @annots_modify.active?
|
73
|
+
rights << UsageRights::Rights::ANNOTS_COPY if @annots_copy.active?
|
74
|
+
rights << UsageRights::Rights::ANNOTS_IMPORT if @annots_import.active?
|
75
|
+
rights << UsageRights::Rights::ANNOTS_EXPORT if @annots_export.active?
|
76
|
+
rights << UsageRights::Rights::ANNOTS_ONLINE if @annots_online.active?
|
77
|
+
rights << UsageRights::Rights::ANNOTS_SUMMARYVIEW if @annots_sumview.active?
|
78
|
+
|
79
|
+
rights << UsageRights::Rights::FORM_FILLIN if @form_fillin.active?
|
80
|
+
rights << UsageRights::Rights::FORM_IMPORT if @form_import.active?
|
81
|
+
rights << UsageRights::Rights::FORM_EXPORT if @form_export.active?
|
82
|
+
rights << UsageRights::Rights::FORM_SUBMITSTANDALONE if @form_submit.active?
|
83
|
+
rights << UsageRights::Rights::FORM_SPAWNTEMPLATE if @form_spawntemplate.active?
|
84
|
+
rights << UsageRights::Rights::FORM_BARCODEPLAINTEXT if @form_barcode.active?
|
85
|
+
rights << UsageRights::Rights::FORM_ONLINE if @form_online.active?
|
86
|
+
|
87
|
+
rights << UsageRights::Rights::SIGNATURE_MODIFY if @signature_modify.active?
|
88
|
+
|
89
|
+
rights << UsageRights::Rights::EF_CREATE if @ef_create.active?
|
90
|
+
rights << UsageRights::Rights::EF_DELETE if @ef_delete.active?
|
91
|
+
rights << UsageRights::Rights::EF_MODIFY if @ef_modify.active?
|
92
|
+
rights << UsageRights::Rights::EF_IMPORT if @ef_import.active?
|
93
|
+
|
94
|
+
begin
|
95
|
+
pdf.enable_usage_rights(*rights)
|
96
|
+
|
97
|
+
set_page_title(@lastpage, "Usage Rights have been enabled")
|
98
|
+
@msg_status.text = "Usage Rights have been enabled for the current document.\n You should consider saving it now."
|
99
|
+
|
100
|
+
@parent.reload
|
101
|
+
rescue Exception => e
|
102
|
+
puts e
|
103
|
+
puts e.backtrace
|
104
|
+
|
105
|
+
set_page_title(@lastpage, "Usage Rights have not been enabled")
|
106
|
+
@msg_status.text = "An error occured during the signature process."
|
107
|
+
end
|
108
|
+
}
|
109
|
+
|
110
|
+
show_all
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def create_intro_page
|
117
|
+
|
118
|
+
intro = <<INTRO
|
119
|
+
You are about to enable Usage Rights for the current PDF document.
|
120
|
+
To enable these features, you need to have an Adobe public/private key pair in your possession.
|
121
|
+
|
122
|
+
Make sure you have adobe.crt and adobe.key located in the current directory.
|
123
|
+
INTRO
|
124
|
+
|
125
|
+
vbox = VBox.new(false, 5)
|
126
|
+
vbox.set_border_width(5)
|
127
|
+
|
128
|
+
lbl = Label.new(intro).set_justify(Gtk::JUSTIFY_LEFT).set_wrap(true)
|
129
|
+
|
130
|
+
vbox.pack_start(lbl, true, true, 0)
|
131
|
+
|
132
|
+
append_page(vbox)
|
133
|
+
set_page_title(vbox, "Usage Rights Wizard")
|
134
|
+
set_page_type(vbox, Assistant::PAGE_INTRO)
|
135
|
+
set_page_complete(vbox, true)
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
def create_rights_selection_page
|
140
|
+
|
141
|
+
vbox = VBox.new(false, 5)
|
142
|
+
|
143
|
+
docframe = Frame.new(" Document ")
|
144
|
+
docframe.border_width = 5
|
145
|
+
docframe.shadow_type = Gtk::SHADOW_IN
|
146
|
+
|
147
|
+
doctable = Table.new(1, 2)
|
148
|
+
doctable.attach(@document_fullsave = CheckButton.new("Full Save").set_active(true), 0, 1, 0, 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
149
|
+
docframe.add(doctable)
|
150
|
+
|
151
|
+
annotsframe = Frame.new(" Annotations ")
|
152
|
+
annotsframe.border_width = 5
|
153
|
+
annotsframe.shadow_type = Gtk::SHADOW_IN
|
154
|
+
|
155
|
+
annotstable = Table.new(4,2)
|
156
|
+
annots =
|
157
|
+
[
|
158
|
+
[ @annots_create = CheckButton.new("Create").set_active(true), @annots_import = CheckButton.new("Import").set_active(true) ],
|
159
|
+
[ @annots_delete = CheckButton.new("Delete").set_active(true), @annots_export = CheckButton.new("Export").set_active(true) ],
|
160
|
+
[ @annots_modify = CheckButton.new("Modify").set_active(true), @annots_online = CheckButton.new("Online").set_active(true) ],
|
161
|
+
[ @annots_copy = CheckButton.new("Copy").set_active(true), @annots_sumview = CheckButton.new("Summary View").set_active(true) ]
|
162
|
+
]
|
163
|
+
|
164
|
+
tt = Tooltips.new.enable
|
165
|
+
tt.set_tip(@annots_create, "test", "")
|
166
|
+
|
167
|
+
row = 0
|
168
|
+
annots.each do |col1, col2|
|
169
|
+
|
170
|
+
annotstable.attach(col1, 0, 1, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
171
|
+
annotstable.attach(col2, 1, 2, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
172
|
+
|
173
|
+
row = row.succ
|
174
|
+
end
|
175
|
+
|
176
|
+
annotsframe.add(annotstable)
|
177
|
+
|
178
|
+
formframe = Frame.new(" Forms ")
|
179
|
+
formframe.border_width = 5
|
180
|
+
formframe.shadow_type = Gtk::SHADOW_IN
|
181
|
+
|
182
|
+
formtable = Table.new(4,2)
|
183
|
+
forms =
|
184
|
+
[
|
185
|
+
[ @form_fillin = CheckButton.new("Fill in").set_active(true), @form_spawntemplate = CheckButton.new("Spawn template").set_active(true) ],
|
186
|
+
[ @form_import = CheckButton.new("Import").set_active(true), @form_barcode = CheckButton.new("Barcode plaintext").set_active(true) ],
|
187
|
+
[ @form_export = CheckButton.new("Export").set_active(true), @form_online = CheckButton.new("Online").set_active(true) ],
|
188
|
+
[ @form_submit = CheckButton.new("Submit stand-alone").set_active(true), nil ]
|
189
|
+
]
|
190
|
+
|
191
|
+
row = 0
|
192
|
+
forms.each do |col1, col2|
|
193
|
+
|
194
|
+
formtable.attach(col1, 0, 1, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
195
|
+
formtable.attach(col2, 1, 2, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
196
|
+
|
197
|
+
row = row.succ
|
198
|
+
end
|
199
|
+
|
200
|
+
formframe.add(formtable)
|
201
|
+
|
202
|
+
signatureframe = Frame.new(" Signature ")
|
203
|
+
signatureframe.border_width = 5
|
204
|
+
signatureframe.shadow_type = Gtk::SHADOW_IN
|
205
|
+
|
206
|
+
signaturetable = Table.new(1, 2)
|
207
|
+
signaturetable.attach(@signature_modify = CheckButton.new("Modify").set_active(true), 0, 1, 0, 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
208
|
+
signatureframe.add(signaturetable)
|
209
|
+
|
210
|
+
efframe = Frame.new(" Embedded files ")
|
211
|
+
efframe.border_width = 5
|
212
|
+
efframe.shadow_type = Gtk::SHADOW_IN
|
213
|
+
|
214
|
+
eftable = Table.new(2,2)
|
215
|
+
efitems =
|
216
|
+
[
|
217
|
+
[ @ef_create = CheckButton.new("Create").set_active(true), @ef_modify = CheckButton.new("Modify").set_active(true) ],
|
218
|
+
[ @ef_delete = CheckButton.new("Delete").set_active(true), @ef_import = CheckButton.new("Import").set_active(true) ]
|
219
|
+
]
|
220
|
+
|
221
|
+
row = 0
|
222
|
+
efitems.each do |col1, col2|
|
223
|
+
|
224
|
+
eftable.attach(col1, 0, 1, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
225
|
+
eftable.attach(col2, 1, 2, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
226
|
+
|
227
|
+
row = row.succ
|
228
|
+
end
|
229
|
+
|
230
|
+
efframe.add(eftable)
|
231
|
+
|
232
|
+
vbox.add(docframe)
|
233
|
+
vbox.add(annotsframe)
|
234
|
+
vbox.add(formframe)
|
235
|
+
vbox.add(signatureframe)
|
236
|
+
vbox.add(efframe)
|
237
|
+
|
238
|
+
append_page(vbox)
|
239
|
+
set_page_title(vbox, "Select Usage Rights to enable")
|
240
|
+
set_page_type(vbox, Assistant::PAGE_CONFIRM)
|
241
|
+
set_page_complete(vbox, true)
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
def create_termination_page
|
246
|
+
|
247
|
+
@lastpage = VBox.new(false, 5)
|
248
|
+
|
249
|
+
@msg_status = Label.new
|
250
|
+
@lastpage.pack_start(@msg_status, true, true, 0)
|
251
|
+
|
252
|
+
append_page(@lastpage)
|
253
|
+
set_page_title(@lastpage, "Usage Rights have not been enabled")
|
254
|
+
set_page_type(@lastpage, Assistant::PAGE_SUMMARY)
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
class SignWizard < Assistant
|
261
|
+
|
262
|
+
INTRO_PAGE = 0
|
263
|
+
KEY_SELECT_PAGE = 1
|
264
|
+
PKCS12_IMPORT_PAGE = 2
|
265
|
+
KEYPAIR_IMPORT_PAGE = 3
|
266
|
+
SIGNATURE_INFO_PAGE = 4
|
267
|
+
SIGNATURE_RESULT_PAGE = 5
|
268
|
+
|
269
|
+
def initialize(parent, pdf)
|
270
|
+
|
271
|
+
super()
|
272
|
+
|
273
|
+
@parent = parent
|
274
|
+
|
275
|
+
@pkey, @cert, @ca = nil, nil, []
|
276
|
+
|
277
|
+
create_intro_page
|
278
|
+
create_key_selection_page
|
279
|
+
create_pkcs12_import_page
|
280
|
+
create_keypair_import_page
|
281
|
+
create_signature_info_page
|
282
|
+
create_termination_page
|
283
|
+
|
284
|
+
set_forward_page_func { |current_page|
|
285
|
+
case current_page
|
286
|
+
when KEY_SELECT_PAGE
|
287
|
+
if @p12button.active? then PKCS12_IMPORT_PAGE else KEYPAIR_IMPORT_PAGE end
|
288
|
+
|
289
|
+
when PKCS12_IMPORT_PAGE, KEYPAIR_IMPORT_PAGE
|
290
|
+
SIGNATURE_INFO_PAGE
|
291
|
+
|
292
|
+
else current_page.succ
|
293
|
+
end
|
294
|
+
}
|
295
|
+
|
296
|
+
signal_connect('delete_event') { self.destroy }
|
297
|
+
signal_connect('cancel') { self.destroy }
|
298
|
+
signal_connect('close') { self.destroy }
|
299
|
+
|
300
|
+
signal_connect('apply') {
|
301
|
+
|
302
|
+
location = @location.text.empty? ? nil : @location.text
|
303
|
+
contact = @email.text.empty? ? nil : @email.text
|
304
|
+
reason = @reason.text.empty? ? nil : @reason.text
|
305
|
+
|
306
|
+
begin
|
307
|
+
pdf.sign(@cert, @pkey, @ca, nil, location, contact, reason)
|
308
|
+
|
309
|
+
set_page_title(@lastpage, "Document has been signed")
|
310
|
+
@msg_status.text = "The document has been signed.\n You should consider saving it now."
|
311
|
+
|
312
|
+
@parent.reload
|
313
|
+
rescue Exception => e
|
314
|
+
puts e
|
315
|
+
puts e.backtrace
|
316
|
+
|
317
|
+
set_page_title(@lastpage, "Document has not been signed")
|
318
|
+
@msg_status.text = "An error occured during the signature process."
|
319
|
+
end
|
320
|
+
}
|
321
|
+
|
322
|
+
show_all
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
private
|
327
|
+
|
328
|
+
def create_intro_page
|
329
|
+
|
330
|
+
intro = <<INTRO
|
331
|
+
You are about to sign the current PDF document.
|
332
|
+
Once the document will be signed, no further modification will be allowed.
|
333
|
+
|
334
|
+
The signature process is based on assymetric cryptography, so you will basically need a public/private RSA key pair (between 1024 and 4096 bits).
|
335
|
+
INTRO
|
336
|
+
|
337
|
+
vbox = VBox.new(false, 5)
|
338
|
+
vbox.set_border_width(5)
|
339
|
+
|
340
|
+
lbl = Label.new(intro).set_justify(Gtk::JUSTIFY_LEFT).set_wrap(true)
|
341
|
+
|
342
|
+
vbox.pack_start(lbl, true, true, 0)
|
343
|
+
|
344
|
+
append_page(vbox)
|
345
|
+
set_page_title(vbox, "Signature Wizard")
|
346
|
+
set_page_type(vbox, Assistant::PAGE_INTRO)
|
347
|
+
set_page_complete(vbox, true)
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
def create_key_selection_page
|
352
|
+
|
353
|
+
vbox = VBox.new(false, 5)
|
354
|
+
|
355
|
+
@p12button = RadioButton.new("Import keys from a PKCS12 container")
|
356
|
+
@rawbutton = RadioButton.new(@p12button, "Import keys from separate PEM/DER encoded files")
|
357
|
+
|
358
|
+
vbox.pack_start(@p12button, true, true, 0)
|
359
|
+
vbox.pack_start(@rawbutton, true, true, 0)
|
360
|
+
|
361
|
+
append_page(vbox)
|
362
|
+
set_page_title(vbox, "Choose a key importation method")
|
363
|
+
set_page_type(vbox, Assistant::PAGE_CONTENT)
|
364
|
+
set_page_complete(vbox, true)
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
def create_pkcs12_import_page
|
369
|
+
|
370
|
+
def get_passwd
|
371
|
+
|
372
|
+
dialog = Dialog.new("Enter passphrase",
|
373
|
+
@parent,
|
374
|
+
Dialog::MODAL,
|
375
|
+
[Stock::OK, Dialog::RESPONSE_OK]
|
376
|
+
)
|
377
|
+
|
378
|
+
pwd_entry = Entry.new.set_visibility(false).show
|
379
|
+
|
380
|
+
dialog.vbox.pack_start(pwd_entry, true, true, 0)
|
381
|
+
|
382
|
+
pwd = (dialog.run == Dialog::RESPONSE_OK) ? pwd_entry.text : ""
|
383
|
+
|
384
|
+
dialog.destroy
|
385
|
+
|
386
|
+
return pwd
|
387
|
+
|
388
|
+
end
|
389
|
+
|
390
|
+
def open_file_dialog(page)
|
391
|
+
|
392
|
+
dialog = FileChooserDialog.new("Open PKCS12 container",
|
393
|
+
@parent,
|
394
|
+
FileChooser::ACTION_OPEN,
|
395
|
+
nil,
|
396
|
+
[Stock::CANCEL, Dialog::RESPONSE_CANCEL],
|
397
|
+
[Stock::OPEN, Dialog::RESPONSE_ACCEPT])
|
398
|
+
filter = FileFilter.new
|
399
|
+
filter.add_pattern("*.pfx")
|
400
|
+
filter.add_pattern("*.p12")
|
401
|
+
|
402
|
+
dialog.filter = filter
|
403
|
+
|
404
|
+
if dialog.run == Dialog::RESPONSE_ACCEPT
|
405
|
+
|
406
|
+
begin
|
407
|
+
p12 = OpenSSL::PKCS12::PKCS12.new(File.open(dialog.filename, 'r').binmode.read, get_passwd)
|
408
|
+
|
409
|
+
if not p12.key.is_a?(OpenSSL::PKey::RSA) then raise TypeError end
|
410
|
+
if not p12.certificate.is_a?(OpenSSL::X509::Certificate) then raise TypeError end
|
411
|
+
|
412
|
+
@pkey = p12.key
|
413
|
+
@cert = p12.certificate
|
414
|
+
@ca = p12.ca_certs
|
415
|
+
|
416
|
+
@p12filename.set_text(dialog.filename)
|
417
|
+
set_page_complete(page, true)
|
418
|
+
|
419
|
+
rescue Exception => e
|
420
|
+
puts e.backtrace
|
421
|
+
error = MessageDialog.new(@parent,
|
422
|
+
Dialog::MODAL,
|
423
|
+
Gtk::MessageDialog::ERROR,
|
424
|
+
Gtk::MessageDialog::BUTTONS_CLOSE,
|
425
|
+
"Error loading file '#{File.basename(dialog.filename)}'")
|
426
|
+
error.run
|
427
|
+
error.destroy
|
428
|
+
|
429
|
+
@pkey, @cert, @ca = nil, nil, []
|
430
|
+
@p12filename.text = ""
|
431
|
+
set_page_complete(page, false)
|
432
|
+
|
433
|
+
end
|
434
|
+
|
435
|
+
end
|
436
|
+
|
437
|
+
dialog.destroy
|
438
|
+
|
439
|
+
end
|
440
|
+
|
441
|
+
vbox = VBox.new(false, 5)
|
442
|
+
|
443
|
+
hbox = HBox.new(false, 5)
|
444
|
+
vbox.pack_start(hbox, true, false, 10)
|
445
|
+
|
446
|
+
@p12filename = Entry.new.set_editable(false).set_sensitive(false)
|
447
|
+
choosebtn = Button.new(Gtk::Stock::OPEN)
|
448
|
+
|
449
|
+
choosebtn.signal_connect('clicked') { open_file_dialog(vbox) }
|
450
|
+
|
451
|
+
hbox.pack_start(@p12filename, true, true, 5)
|
452
|
+
hbox.pack_start(choosebtn, false, false, 5)
|
453
|
+
|
454
|
+
append_page(vbox)
|
455
|
+
set_page_title(vbox, "Import a PKCS12 container")
|
456
|
+
set_page_type(vbox, Assistant::PAGE_CONTENT)
|
457
|
+
|
458
|
+
end
|
459
|
+
|
460
|
+
def create_keypair_import_page
|
461
|
+
|
462
|
+
def open_pkey_dialog(page)
|
463
|
+
|
464
|
+
dialog = FileChooserDialog.new("Choose a private RSA key",
|
465
|
+
@parent,
|
466
|
+
FileChooser::ACTION_OPEN,
|
467
|
+
nil,
|
468
|
+
[Stock::CANCEL, Dialog::RESPONSE_CANCEL],
|
469
|
+
[Stock::OPEN, Dialog::RESPONSE_ACCEPT])
|
470
|
+
filter = FileFilter.new
|
471
|
+
filter.add_pattern("*.key")
|
472
|
+
filter.add_pattern("*.pem")
|
473
|
+
filter.add_pattern("*.der")
|
474
|
+
|
475
|
+
dialog.set_filter(filter)
|
476
|
+
|
477
|
+
if dialog.run == Dialog::RESPONSE_ACCEPT
|
478
|
+
|
479
|
+
begin
|
480
|
+
@pkey = OpenSSL::PKey::RSA.new(File.open(dialog.filename, 'r').binmode.read)
|
481
|
+
|
482
|
+
@pkeyfilename.set_text(dialog.filename)
|
483
|
+
if @cert then set_page_complete(page, true) end
|
484
|
+
|
485
|
+
rescue Exception => e
|
486
|
+
puts e.backtrace
|
487
|
+
error = MessageDialog.new(@parent,
|
488
|
+
Dialog::MODAL,
|
489
|
+
Gtk::MessageDialog::ERROR,
|
490
|
+
Gtk::MessageDialog::BUTTONS_CLOSE,
|
491
|
+
"Error loading file '#{File.basename(dialog.filename)}'")
|
492
|
+
error.run
|
493
|
+
error.destroy
|
494
|
+
|
495
|
+
@pkey = nil
|
496
|
+
@pkeyfilename.text = ""
|
497
|
+
set_page_complete(page, false)
|
498
|
+
|
499
|
+
ensure
|
500
|
+
@ca = [] # Shall be added to the GUI
|
501
|
+
end
|
502
|
+
|
503
|
+
end
|
504
|
+
|
505
|
+
dialog.destroy
|
506
|
+
|
507
|
+
end
|
508
|
+
|
509
|
+
def open_cert_dialog(page)
|
510
|
+
|
511
|
+
dialog = FileChooserDialog.new("Choose a private RSA key",
|
512
|
+
@parent,
|
513
|
+
FileChooser::ACTION_OPEN,
|
514
|
+
nil,
|
515
|
+
[Stock::CANCEL, Dialog::RESPONSE_CANCEL],
|
516
|
+
[Stock::OPEN, Dialog::RESPONSE_ACCEPT])
|
517
|
+
filter = FileFilter.new
|
518
|
+
filter.add_pattern("*.crt")
|
519
|
+
filter.add_pattern("*.cer")
|
520
|
+
filter.add_pattern("*.pem")
|
521
|
+
filter.add_pattern("*.der")
|
522
|
+
|
523
|
+
dialog.set_filter(filter)
|
524
|
+
|
525
|
+
if dialog.run == Dialog::RESPONSE_ACCEPT
|
526
|
+
|
527
|
+
begin
|
528
|
+
@cert = OpenSSL::X509::Certificate.new(File.open(dialog.filename, 'r').binmode.read)
|
529
|
+
|
530
|
+
@certfilename.set_text(dialog.filename)
|
531
|
+
if @pkey then set_page_complete(page, true) end
|
532
|
+
|
533
|
+
rescue Exception => e
|
534
|
+
puts e.backtrace
|
535
|
+
error = MessageDialog.new(@parent,
|
536
|
+
Dialog::MODAL,
|
537
|
+
Gtk::MessageDialog::ERROR,
|
538
|
+
Gtk::MessageDialog::BUTTONS_CLOSE,
|
539
|
+
"Error loading file '#{File.basename(dialog.filename)}'")
|
540
|
+
error.run
|
541
|
+
error.destroy
|
542
|
+
|
543
|
+
@cert = nil
|
544
|
+
@certfilename.text = ""
|
545
|
+
set_page_complete(page, false)
|
546
|
+
|
547
|
+
ensure
|
548
|
+
@ca = [] # Shall be added to the GUI
|
549
|
+
end
|
550
|
+
|
551
|
+
end
|
552
|
+
|
553
|
+
dialog.destroy
|
554
|
+
|
555
|
+
end
|
556
|
+
|
557
|
+
labels =
|
558
|
+
[
|
559
|
+
[ "Private RSA key:", @pkeyfilename = Entry.new, pkeychoosebtn = Button.new(Gtk::Stock::OPEN) ],
|
560
|
+
[ "Public certificate:", @certfilename = Entry.new, certchoosebtn = Button.new(Gtk::Stock::OPEN) ]
|
561
|
+
]
|
562
|
+
|
563
|
+
row = 0
|
564
|
+
table = Table.new(2, 3)
|
565
|
+
labels.each do |lbl, entry, btn|
|
566
|
+
|
567
|
+
entry.editable = entry.sensitive = false
|
568
|
+
|
569
|
+
table.attach(Label.new(lbl).set_alignment(1,0), 0, 1, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
570
|
+
table.attach(entry, 1, 2, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
571
|
+
table.attach(btn, 2, 3, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
572
|
+
|
573
|
+
row = row.succ
|
574
|
+
end
|
575
|
+
|
576
|
+
pkeychoosebtn.signal_connect('clicked') { open_pkey_dialog(table) }
|
577
|
+
certchoosebtn.signal_connect('clicked') { open_cert_dialog(table) }
|
578
|
+
|
579
|
+
append_page(table)
|
580
|
+
set_page_title(table, "Import a public/private key pair")
|
581
|
+
set_page_type(table, Assistant::PAGE_CONTENT)
|
582
|
+
|
583
|
+
end
|
584
|
+
|
585
|
+
def create_signature_info_page
|
586
|
+
|
587
|
+
vbox = VBox.new(false, 5)
|
588
|
+
|
589
|
+
lbl = Label.new("Here are a few optional information you can add with your signature.")
|
590
|
+
vbox.pack_start(lbl, true, true, 0)
|
591
|
+
|
592
|
+
labels =
|
593
|
+
[
|
594
|
+
[ "Location:", @location = Entry.new ],
|
595
|
+
[ "Contact:", @email = Entry.new ],
|
596
|
+
[ "Reason:", @reason = Entry.new ]
|
597
|
+
]
|
598
|
+
|
599
|
+
row = 0
|
600
|
+
table = Table.new(4, 3)
|
601
|
+
labels.each do |label|
|
602
|
+
|
603
|
+
table.attach(Label.new(label[0]).set_alignment(1,0), 0, 1, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
604
|
+
table.attach(label[1], 1, 2, row, row + 1, Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK, 4, 4)
|
605
|
+
|
606
|
+
row = row.succ
|
607
|
+
end
|
608
|
+
|
609
|
+
vbox.pack_start(table, true, true, 0)
|
610
|
+
|
611
|
+
append_page(vbox)
|
612
|
+
set_page_title(vbox, "Fill in signature details")
|
613
|
+
set_page_type(vbox, Assistant::PAGE_CONFIRM)
|
614
|
+
set_page_complete(vbox, true)
|
615
|
+
|
616
|
+
end
|
617
|
+
|
618
|
+
def create_termination_page
|
619
|
+
|
620
|
+
@lastpage = VBox.new(false, 5)
|
621
|
+
|
622
|
+
@msg_status = Label.new
|
623
|
+
@lastpage.pack_start(@msg_status, true, true, 0)
|
624
|
+
|
625
|
+
append_page(@lastpage)
|
626
|
+
set_page_title(@lastpage, "Document has not been signed")
|
627
|
+
set_page_type(@lastpage, Assistant::PAGE_SUMMARY)
|
628
|
+
|
629
|
+
end
|
630
|
+
|
631
|
+
end
|
632
|
+
|
633
|
+
end
|
634
|
+
|
635
|
+
end
|