origamindee 3.0.0
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 +7 -0
- data/CHANGELOG.md +89 -0
- data/COPYING.LESSER +165 -0
- data/README.md +131 -0
- data/bin/config/pdfcop.conf.yml +236 -0
- data/bin/pdf2pdfa +87 -0
- data/bin/pdf2ruby +333 -0
- data/bin/pdfcop +476 -0
- data/bin/pdfdecompress +97 -0
- data/bin/pdfdecrypt +91 -0
- data/bin/pdfencrypt +113 -0
- data/bin/pdfexplode +223 -0
- data/bin/pdfextract +277 -0
- data/bin/pdfmetadata +143 -0
- data/bin/pdfsh +12 -0
- data/bin/shell/console.rb +128 -0
- data/bin/shell/hexdump.rb +59 -0
- data/bin/shell/irbrc +69 -0
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/examples/events/events.rb +72 -0
- data/examples/flash/flash.rb +37 -0
- data/examples/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami/3d.rb +364 -0
- data/lib/origami/acroform.rb +321 -0
- data/lib/origami/actions.rb +318 -0
- data/lib/origami/annotations.rb +711 -0
- data/lib/origami/array.rb +242 -0
- data/lib/origami/boolean.rb +90 -0
- data/lib/origami/catalog.rb +418 -0
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/compound.rb +161 -0
- data/lib/origami/destinations.rb +252 -0
- data/lib/origami/dictionary.rb +192 -0
- data/lib/origami/encryption.rb +1084 -0
- data/lib/origami/extensions/fdf.rb +347 -0
- data/lib/origami/extensions/ppklite.rb +422 -0
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters/ascii.rb +211 -0
- data/lib/origami/filters/ccitt/tables.rb +267 -0
- data/lib/origami/filters/ccitt.rb +357 -0
- data/lib/origami/filters/crypt.rb +38 -0
- data/lib/origami/filters/dct.rb +54 -0
- data/lib/origami/filters/flate.rb +69 -0
- data/lib/origami/filters/jbig2.rb +57 -0
- data/lib/origami/filters/jpx.rb +47 -0
- data/lib/origami/filters/lzw.rb +170 -0
- data/lib/origami/filters/predictors.rb +292 -0
- data/lib/origami/filters/runlength.rb +129 -0
- data/lib/origami/filters.rb +364 -0
- data/lib/origami/font.rb +196 -0
- data/lib/origami/functions.rb +79 -0
- data/lib/origami/graphics/colors.rb +230 -0
- data/lib/origami/graphics/instruction.rb +98 -0
- data/lib/origami/graphics/path.rb +182 -0
- data/lib/origami/graphics/patterns.rb +174 -0
- data/lib/origami/graphics/render.rb +62 -0
- data/lib/origami/graphics/state.rb +149 -0
- data/lib/origami/graphics/text.rb +225 -0
- data/lib/origami/graphics/xobject.rb +918 -0
- data/lib/origami/graphics.rb +38 -0
- data/lib/origami/header.rb +75 -0
- data/lib/origami/javascript.rb +713 -0
- data/lib/origami/linearization.rb +330 -0
- data/lib/origami/metadata.rb +172 -0
- data/lib/origami/name.rb +135 -0
- data/lib/origami/null.rb +65 -0
- data/lib/origami/numeric.rb +181 -0
- data/lib/origami/obfuscation.rb +245 -0
- data/lib/origami/object.rb +760 -0
- data/lib/origami/optionalcontent.rb +183 -0
- data/lib/origami/outline.rb +54 -0
- data/lib/origami/outputintents.rb +85 -0
- data/lib/origami/page.rb +722 -0
- data/lib/origami/parser.rb +269 -0
- data/lib/origami/parsers/fdf.rb +56 -0
- data/lib/origami/parsers/pdf/lazy.rb +176 -0
- data/lib/origami/parsers/pdf/linear.rb +122 -0
- data/lib/origami/parsers/pdf.rb +118 -0
- data/lib/origami/parsers/ppklite.rb +57 -0
- data/lib/origami/pdf.rb +1108 -0
- data/lib/origami/reference.rb +134 -0
- data/lib/origami/signature.rb +702 -0
- data/lib/origami/stream.rb +705 -0
- data/lib/origami/string.rb +444 -0
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +190 -0
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +100 -0
- data/lib/origami/xfa/config.rb +453 -0
- data/lib/origami/xfa/connectionset.rb +146 -0
- data/lib/origami/xfa/datasets.rb +49 -0
- data/lib/origami/xfa/localeset.rb +42 -0
- data/lib/origami/xfa/package.rb +59 -0
- data/lib/origami/xfa/pdf.rb +73 -0
- data/lib/origami/xfa/signature.rb +42 -0
- data/lib/origami/xfa/sourceset.rb +43 -0
- data/lib/origami/xfa/stylesheet.rb +44 -0
- data/lib/origami/xfa/template.rb +1691 -0
- data/lib/origami/xfa/xdc.rb +42 -0
- data/lib/origami/xfa/xfa.rb +146 -0
- data/lib/origami/xfa/xfdf.rb +43 -0
- data/lib/origami/xfa/xmpmeta.rb +43 -0
- data/lib/origami/xfa.rb +62 -0
- data/lib/origami/xreftable.rb +557 -0
- data/lib/origami.rb +47 -0
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +36 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +68 -0
- data/test/test_forms.rb +30 -0
- data/test/test_native_types.rb +83 -0
- data/test/test_object_tree.rb +33 -0
- data/test/test_pages.rb +60 -0
- data/test/test_pdf.rb +20 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +102 -0
- data/test/test_pdf_parse.rb +134 -0
- data/test/test_pdf_parse_lazy.rb +69 -0
- data/test/test_pdf_sign.rb +97 -0
- data/test/test_streams.rb +184 -0
- data/test/test_xrefs.rb +67 -0
- metadata +280 -0
@@ -0,0 +1,702 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
5
|
+
|
6
|
+
Origami is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
Origami is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'openssl'
|
22
|
+
require 'digest/sha1'
|
23
|
+
|
24
|
+
module Origami
|
25
|
+
|
26
|
+
class SignatureError < Error #:nodoc:
|
27
|
+
end
|
28
|
+
|
29
|
+
class PDF
|
30
|
+
#
|
31
|
+
# Verify a document signature.
|
32
|
+
# _:trusted_certs_: an array of trusted X509 certificates.
|
33
|
+
# _:use_system_store_: use the system store for certificate authorities.
|
34
|
+
# _:allow_self_signed_: allow self-signed certificates in the verification chain.
|
35
|
+
# _verify_cb_: block called when encountering a certificate that cannot be verified.
|
36
|
+
# Passed argument in the OpenSSL::X509::StoreContext.
|
37
|
+
#
|
38
|
+
def verify(trusted_certs: [],
|
39
|
+
use_system_store: false,
|
40
|
+
allow_self_signed: false,
|
41
|
+
&verify_cb)
|
42
|
+
|
43
|
+
digsig = self.signature
|
44
|
+
digsig = digsig.cast_to(Signature::DigitalSignature) unless digsig.is_a?(Signature::DigitalSignature)
|
45
|
+
|
46
|
+
signature = digsig.signature_data
|
47
|
+
chain = digsig.certificate_chain
|
48
|
+
subfilter = digsig.SubFilter.value
|
49
|
+
|
50
|
+
store = OpenSSL::X509::Store.new
|
51
|
+
store.set_default_paths if use_system_store
|
52
|
+
trusted_certs.each { |ca| store.add_cert(ca) }
|
53
|
+
|
54
|
+
store.verify_callback = -> (success, ctx) {
|
55
|
+
return true if success
|
56
|
+
|
57
|
+
error = ctx.error
|
58
|
+
is_self_signed = (error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
|
59
|
+
error == OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN)
|
60
|
+
|
61
|
+
return true if is_self_signed && allow_self_signed && verify_cb.nil?
|
62
|
+
|
63
|
+
verify_cb.call(ctx) unless verify_cb.nil?
|
64
|
+
}
|
65
|
+
|
66
|
+
data = extract_signed_data(digsig)
|
67
|
+
Signature.verify(subfilter.to_s, data, signature, store, chain)
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Sign the document with the given key and x509 certificate.
|
72
|
+
# _certificate_:: The X509 certificate containing the public key.
|
73
|
+
# _key_:: The private key associated with the certificate.
|
74
|
+
# _method_:: The PDF signature identifier.
|
75
|
+
# _ca_:: Optional CA certificates used to sign the user certificate.
|
76
|
+
# _annotation_:: Annotation associated with the signature.
|
77
|
+
# _issuer_:: Issuer name.
|
78
|
+
# _location_:: Signature location.
|
79
|
+
# _contact_:: Signer contact.
|
80
|
+
# _reason_:: Signing reason.
|
81
|
+
#
|
82
|
+
def sign(certificate, key,
|
83
|
+
method: Signature::PKCS7_DETACHED,
|
84
|
+
ca: [],
|
85
|
+
annotation: nil,
|
86
|
+
issuer: nil,
|
87
|
+
location: nil,
|
88
|
+
contact: nil,
|
89
|
+
reason: nil)
|
90
|
+
|
91
|
+
unless certificate.is_a?(OpenSSL::X509::Certificate)
|
92
|
+
raise TypeError, "A OpenSSL::X509::Certificate object must be passed."
|
93
|
+
end
|
94
|
+
|
95
|
+
unless key.is_a?(OpenSSL::PKey::RSA)
|
96
|
+
raise TypeError, "A OpenSSL::PKey::RSA object must be passed."
|
97
|
+
end
|
98
|
+
|
99
|
+
unless ca.is_a?(::Array)
|
100
|
+
raise TypeError, "Expected an Array of CA certificates."
|
101
|
+
end
|
102
|
+
|
103
|
+
unless annotation.nil? or annotation.is_a?(Annotation::Widget::Signature)
|
104
|
+
raise TypeError, "Expected a Annotation::Widget::Signature object."
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# XXX: Currently signing a linearized document will result in a broken document.
|
109
|
+
# Delinearize the document first until we find a proper way to handle this case.
|
110
|
+
#
|
111
|
+
if self.linearized?
|
112
|
+
self.delinearize!
|
113
|
+
end
|
114
|
+
|
115
|
+
digsig = Signature::DigitalSignature.new.set_indirect(true)
|
116
|
+
|
117
|
+
if annotation.nil?
|
118
|
+
annotation = Annotation::Widget::Signature.new
|
119
|
+
annotation.Rect = Rectangle[:llx => 0.0, :lly => 0.0, :urx => 0.0, :ury => 0.0]
|
120
|
+
end
|
121
|
+
|
122
|
+
annotation.V = digsig
|
123
|
+
add_fields(annotation)
|
124
|
+
self.Catalog.AcroForm.SigFlags =
|
125
|
+
InteractiveForm::SigFlags::SIGNATURES_EXIST | InteractiveForm::SigFlags::APPEND_ONLY
|
126
|
+
|
127
|
+
digsig.Type = :Sig
|
128
|
+
digsig.Contents = HexaString.new("\x00" * Signature::required_size(method, certificate, key, ca))
|
129
|
+
digsig.Filter = :"Adobe.PPKLite"
|
130
|
+
digsig.SubFilter = Name.new(method)
|
131
|
+
digsig.ByteRange = [0, 0, 0, 0]
|
132
|
+
digsig.Name = issuer
|
133
|
+
|
134
|
+
digsig.Location = HexaString.new(location) if location
|
135
|
+
digsig.ContactInfo = HexaString.new(contact) if contact
|
136
|
+
digsig.Reason = HexaString.new(reason) if reason
|
137
|
+
|
138
|
+
# PKCS1 signatures require a Cert entry.
|
139
|
+
if method == Signature::PKCS1_RSA_SHA1
|
140
|
+
digsig.Cert =
|
141
|
+
if ca.empty?
|
142
|
+
HexaString.new(certificate.to_der)
|
143
|
+
else
|
144
|
+
[ HexaString.new(certificate.to_der) ] + ca.map{ |crt| HexaString.new(crt.to_der) }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Flattening the PDF to get file view.
|
150
|
+
#
|
151
|
+
compile
|
152
|
+
|
153
|
+
#
|
154
|
+
# Creating an empty Xref table to compute signature byte range.
|
155
|
+
#
|
156
|
+
rebuild_dummy_xrefs
|
157
|
+
|
158
|
+
sig_offset = get_object_offset(digsig.no, digsig.generation) + digsig.signature_offset
|
159
|
+
|
160
|
+
digsig.ByteRange[0] = 0
|
161
|
+
digsig.ByteRange[1] = sig_offset
|
162
|
+
digsig.ByteRange[2] = sig_offset + digsig.Contents.to_s.bytesize
|
163
|
+
|
164
|
+
until digsig.ByteRange[3] == filesize - digsig.ByteRange[2]
|
165
|
+
digsig.ByteRange[3] = filesize - digsig.ByteRange[2]
|
166
|
+
end
|
167
|
+
|
168
|
+
# From that point on, the file size remains constant
|
169
|
+
|
170
|
+
#
|
171
|
+
# Correct Xrefs variations caused by ByteRange modifications.
|
172
|
+
#
|
173
|
+
rebuild_xrefs
|
174
|
+
|
175
|
+
file_data = output()
|
176
|
+
signable_data = file_data[digsig.ByteRange[0],digsig.ByteRange[1]] +
|
177
|
+
file_data[digsig.ByteRange[2],digsig.ByteRange[3]]
|
178
|
+
|
179
|
+
#
|
180
|
+
# Computes and inserts the signature.
|
181
|
+
#
|
182
|
+
signature = Signature.compute(method, signable_data, certificate, key, ca)
|
183
|
+
digsig.Contents[0, signature.size] = signature
|
184
|
+
|
185
|
+
#
|
186
|
+
# No more modification are allowed after signing.
|
187
|
+
#
|
188
|
+
self.freeze
|
189
|
+
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# Returns whether the document contains a digital signature.
|
193
|
+
#
|
194
|
+
def signed?
|
195
|
+
begin
|
196
|
+
self.Catalog.AcroForm.is_a?(Dictionary) and
|
197
|
+
self.Catalog.AcroForm.SigFlags.is_a?(Integer) and
|
198
|
+
(self.Catalog.AcroForm.SigFlags & InteractiveForm::SigFlags::SIGNATURES_EXIST != 0)
|
199
|
+
rescue InvalidReferenceError
|
200
|
+
false
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
#
|
205
|
+
# Enable the document Usage Rights.
|
206
|
+
# _rights_:: list of rights defined in UsageRights::Rights
|
207
|
+
#
|
208
|
+
def enable_usage_rights(cert, pkey, *rights)
|
209
|
+
|
210
|
+
# Always uses a detached PKCS7 signature for UR.
|
211
|
+
method = Signature::PKCS7_DETACHED
|
212
|
+
|
213
|
+
#
|
214
|
+
# Load key pair
|
215
|
+
#
|
216
|
+
key = pkey.is_a?(OpenSSL::PKey::RSA) ? pkey : OpenSSL::PKey::RSA.new(pkey)
|
217
|
+
certificate = cert.is_a?(OpenSSL::X509::Certificate) ? cert : OpenSSL::X509::Certificate.new(cert)
|
218
|
+
|
219
|
+
#
|
220
|
+
# Forge digital signature dictionary
|
221
|
+
#
|
222
|
+
digsig = Signature::DigitalSignature.new.set_indirect(true)
|
223
|
+
|
224
|
+
self.Catalog.AcroForm ||= InteractiveForm.new
|
225
|
+
#self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::APPEND_ONLY
|
226
|
+
|
227
|
+
digsig.Type = :Sig
|
228
|
+
digsig.Contents = HexaString.new("\x00" * Signature.required_size(method, certificate, key, []))
|
229
|
+
digsig.Filter = :"Adobe.PPKLite"
|
230
|
+
digsig.Name = "ARE Acrobat Product v8.0 P23 0002337"
|
231
|
+
digsig.SubFilter = Name.new(method )
|
232
|
+
digsig.ByteRange = [0, 0, 0, 0]
|
233
|
+
|
234
|
+
sigref = Signature::Reference.new
|
235
|
+
sigref.Type = :SigRef
|
236
|
+
sigref.TransformMethod = :UR3
|
237
|
+
sigref.Data = self.Catalog
|
238
|
+
|
239
|
+
sigref.TransformParams = UsageRights::TransformParams.new
|
240
|
+
sigref.TransformParams.P = true
|
241
|
+
sigref.TransformParams.Type = :TransformParams
|
242
|
+
sigref.TransformParams.V = UsageRights::TransformParams::VERSION
|
243
|
+
|
244
|
+
rights.each do |right|
|
245
|
+
sigref.TransformParams[right.first] ||= []
|
246
|
+
sigref.TransformParams[right.first].concat(right[1..-1])
|
247
|
+
end
|
248
|
+
|
249
|
+
digsig.Reference = [ sigref ]
|
250
|
+
|
251
|
+
self.Catalog.Perms ||= Perms.new
|
252
|
+
self.Catalog.Perms.UR3 = digsig
|
253
|
+
|
254
|
+
#
|
255
|
+
# Flattening the PDF to get file view.
|
256
|
+
#
|
257
|
+
compile
|
258
|
+
|
259
|
+
#
|
260
|
+
# Creating an empty Xref table to compute signature byte range.
|
261
|
+
#
|
262
|
+
rebuild_dummy_xrefs
|
263
|
+
|
264
|
+
sig_offset = get_object_offset(digsig.no, digsig.generation) + digsig.signature_offset
|
265
|
+
|
266
|
+
digsig.ByteRange[0] = 0
|
267
|
+
digsig.ByteRange[1] = sig_offset
|
268
|
+
digsig.ByteRange[2] = sig_offset + digsig.Contents.size
|
269
|
+
|
270
|
+
until digsig.ByteRange[3] == filesize - digsig.ByteRange[2]
|
271
|
+
digsig.ByteRange[3] = filesize - digsig.ByteRange[2]
|
272
|
+
end
|
273
|
+
|
274
|
+
# From that point on, the file size remains constant
|
275
|
+
|
276
|
+
#
|
277
|
+
# Correct Xrefs variations caused by ByteRange modifications.
|
278
|
+
#
|
279
|
+
rebuild_xrefs
|
280
|
+
|
281
|
+
file_data = output()
|
282
|
+
signable_data = file_data[digsig.ByteRange[0],digsig.ByteRange[1]] +
|
283
|
+
file_data[digsig.ByteRange[2],digsig.ByteRange[3]]
|
284
|
+
|
285
|
+
signature = Signature.compute(method, signable_data, certificate, key, [])
|
286
|
+
digsig.Contents[0, signature.size] = signature
|
287
|
+
|
288
|
+
#
|
289
|
+
# No more modification are allowed after signing.
|
290
|
+
#
|
291
|
+
self.freeze
|
292
|
+
end
|
293
|
+
|
294
|
+
def usage_rights?
|
295
|
+
not self.Catalog.Perms.nil? and
|
296
|
+
(not self.Catalog.Perms.has_key?(:UR3) or not self.Catalog.Perms.has_key?(:UR))
|
297
|
+
end
|
298
|
+
|
299
|
+
def signature
|
300
|
+
raise SignatureError, "Not a signed document" unless self.signed?
|
301
|
+
|
302
|
+
self.each_field do |field|
|
303
|
+
return field.V if field.FT == :Sig and field.V.is_a?(Dictionary)
|
304
|
+
end
|
305
|
+
|
306
|
+
raise SignatureError, "Cannot find digital signature"
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
|
311
|
+
#
|
312
|
+
# Verifies the ByteRange field of a digital signature and returned the signed data.
|
313
|
+
#
|
314
|
+
def extract_signed_data(digsig)
|
315
|
+
# Computes the boundaries of the Contents field.
|
316
|
+
start_sig = digsig[:Contents].file_offset
|
317
|
+
|
318
|
+
stream = StringScanner.new(self.original_data)
|
319
|
+
stream.pos = digsig[:Contents].file_offset
|
320
|
+
Object.typeof(stream).parse(stream)
|
321
|
+
end_sig = stream.pos
|
322
|
+
stream.terminate
|
323
|
+
|
324
|
+
r1, r2 = digsig.ranges
|
325
|
+
if r1.begin != 0 or
|
326
|
+
r2.end != self.original_data.size or
|
327
|
+
r1.end != start_sig or
|
328
|
+
r2.begin != end_sig
|
329
|
+
|
330
|
+
raise SignatureError, "Invalid signature byte range"
|
331
|
+
end
|
332
|
+
|
333
|
+
self.original_data[r1] + self.original_data[r2]
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
class Perms < Dictionary
|
339
|
+
include StandardObject
|
340
|
+
|
341
|
+
field :DocMDP, :Type => Dictionary
|
342
|
+
field :UR, :Type => Dictionary
|
343
|
+
field :UR3, :Type => Dictionary, :Version => "1.6"
|
344
|
+
end
|
345
|
+
|
346
|
+
module Signature
|
347
|
+
|
348
|
+
PKCS1_RSA_SHA1 = "adbe.x509.rsa_sha1"
|
349
|
+
PKCS7_SHA1 = "adbe.pkcs7.sha1"
|
350
|
+
PKCS7_DETACHED = "adbe.pkcs7.detached"
|
351
|
+
|
352
|
+
#
|
353
|
+
# PKCS1 class used for adbe.x509.rsa_sha1.
|
354
|
+
#
|
355
|
+
class PKCS1
|
356
|
+
class PKCS1Error < SignatureError; end
|
357
|
+
|
358
|
+
def initialize(signature)
|
359
|
+
@signature_object = decode_pkcs1(signature)
|
360
|
+
end
|
361
|
+
|
362
|
+
def verify(certificate, chain, store, data)
|
363
|
+
store.verify(certificate, chain) and certificate.public_key.verify(OpenSSL::Digest::SHA1.new, @signature_object.value, data)
|
364
|
+
end
|
365
|
+
|
366
|
+
def self.sign(certificate, key, data)
|
367
|
+
raise PKCS1Error, "Invalid key for certificate" unless certificate.check_private_key(key)
|
368
|
+
|
369
|
+
self.new encode_pkcs1 key.sign(OpenSSL::Digest::SHA1.new, data)
|
370
|
+
end
|
371
|
+
|
372
|
+
def to_der
|
373
|
+
@signature_object.to_der
|
374
|
+
end
|
375
|
+
|
376
|
+
private
|
377
|
+
|
378
|
+
def decode_pkcs1(data)
|
379
|
+
#
|
380
|
+
# Extracts the first ASN.1 object from the data and discards the rest.
|
381
|
+
# Must be an octet string.
|
382
|
+
#
|
383
|
+
signature_len = 0
|
384
|
+
OpenSSL::ASN1.traverse(data) do |_, offset, hdr_len, len, _, _, tag|
|
385
|
+
raise PKCS1Error, "Invalid PKCS1 object, expected an ASN.1 octet string" unless tag == OpenSSL::ASN1::OCTET_STRING
|
386
|
+
|
387
|
+
signature_len = offset + hdr_len + len
|
388
|
+
break
|
389
|
+
end
|
390
|
+
|
391
|
+
OpenSSL::ASN1.decode(data[0, signature_len])
|
392
|
+
end
|
393
|
+
|
394
|
+
def self.encode_pkcs1(data)
|
395
|
+
OpenSSL::ASN1::OctetString.new(data).to_der
|
396
|
+
end
|
397
|
+
private_class_method :encode_pkcs1
|
398
|
+
end
|
399
|
+
|
400
|
+
def self.verify(method, data, signature, store, chain)
|
401
|
+
case method
|
402
|
+
when PKCS7_DETACHED
|
403
|
+
pkcs7 = OpenSSL::PKCS7.new(signature)
|
404
|
+
raise SignatureError, "Not a PKCS7 detached signature" unless pkcs7.detached?
|
405
|
+
pkcs7.verify([], store, data, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
|
406
|
+
|
407
|
+
when PKCS7_SHA1
|
408
|
+
pkcs7 = OpenSSL::PKCS7.new(signature)
|
409
|
+
pkcs7.verify([], store, nil, OpenSSL::PKCS7::BINARY) and pkcs7.data == Digest::SHA1.digest(data)
|
410
|
+
|
411
|
+
when PKCS1_RSA_SHA1
|
412
|
+
raise SignatureError, "Cannot verify RSA signature without a certificate" if chain.empty?
|
413
|
+
cert = chain.shift
|
414
|
+
pkcs1 = PKCS1.new(signature)
|
415
|
+
pkcs1.verify(cert, chain, store, data)
|
416
|
+
|
417
|
+
else
|
418
|
+
raise NotImplementedError, "Unsupported signature method #{method.inspect}"
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
#
|
423
|
+
# Computes the required size in bytes for storing the signature.
|
424
|
+
#
|
425
|
+
def self.required_size(method, certificate, key, ca)
|
426
|
+
self.compute(method, "", certificate, key, ca).size
|
427
|
+
end
|
428
|
+
|
429
|
+
#
|
430
|
+
# Computes the signature using the specified subfilter method.
|
431
|
+
#
|
432
|
+
def self.compute(method, data, certificate, key, ca)
|
433
|
+
case method
|
434
|
+
when PKCS7_DETACHED
|
435
|
+
OpenSSL::PKCS7.sign(certificate, key, data, ca, OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
|
436
|
+
|
437
|
+
when PKCS7_SHA1
|
438
|
+
OpenSSL::PKCS7.sign(certificate, key, Digest::SHA1.digest(data), ca, OpenSSL::PKCS7::BINARY).to_der
|
439
|
+
|
440
|
+
when PKCS1_RSA_SHA1
|
441
|
+
PKCS1.sign(certificate, key, data).to_der
|
442
|
+
|
443
|
+
else
|
444
|
+
raise NotImplementedError, "Unsupported signature method #{method.inspect}"
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
#
|
449
|
+
# Class representing a signature which can be embedded in DigitalSignature dictionary.
|
450
|
+
# It must be a direct object.
|
451
|
+
#
|
452
|
+
class Reference < Dictionary
|
453
|
+
include StandardObject
|
454
|
+
|
455
|
+
add_type_signature :Type => :SigRef
|
456
|
+
|
457
|
+
field :Type, :Type => Name, :Default => :SigRef
|
458
|
+
field :TransformMethod, :Type => Name, :Default => :DocMDP, :Required => true
|
459
|
+
field :TransformParams, :Type => Dictionary
|
460
|
+
field :Data, :Type => Object
|
461
|
+
field :DigestMethod, :Type => Name, :Default => :MD5
|
462
|
+
field :DigestValue, :Type => String
|
463
|
+
field :DigestLocation, :Type => Array
|
464
|
+
|
465
|
+
def initialize(hash = {}, parser = nil)
|
466
|
+
set_indirect(false)
|
467
|
+
|
468
|
+
super(hash, parser)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
class BuildData < Dictionary
|
473
|
+
include StandardObject
|
474
|
+
|
475
|
+
field :Name, :Type => Name, :Version => "1.5"
|
476
|
+
field :Date, :Type => String, :Version => "1.5"
|
477
|
+
field :R, :Type => Number, :Version => "1.5"
|
478
|
+
field :PreRelease, :Type => Boolean, :Default => false, :Version => "1.5"
|
479
|
+
field :OS, :Type => Array, :Version => "1.5"
|
480
|
+
field :NonEFontNoWarn, :Type => Boolean, :Version => "1.5"
|
481
|
+
field :TrustedMode, :Type => Boolean, :Version => "1.5"
|
482
|
+
field :V, :Type => Number, :Version => "1.5"
|
483
|
+
|
484
|
+
def initialize(hash = {}, parser = nil)
|
485
|
+
set_indirect(false)
|
486
|
+
|
487
|
+
super(hash, parser)
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
class AppData < BuildData
|
492
|
+
field :REx, :Type => String, :Version => "1.6"
|
493
|
+
end
|
494
|
+
|
495
|
+
class SigQData < BuildData
|
496
|
+
field :Preview, :Type => Boolean, :Default => false, :Version => "1.7"
|
497
|
+
end
|
498
|
+
|
499
|
+
class BuildProperties < Dictionary
|
500
|
+
include StandardObject
|
501
|
+
|
502
|
+
field :Filter, :Type => BuildData, :Version => "1.5"
|
503
|
+
field :PubSec, :Type => BuildData, :Version => "1.5"
|
504
|
+
field :App, :Type => AppData, :Version => "1.5"
|
505
|
+
field :SigQ, :Type => SigQData, :Version => "1.7"
|
506
|
+
|
507
|
+
def initialize(hash = {}, parser = nil)
|
508
|
+
set_indirect(false)
|
509
|
+
|
510
|
+
super(hash, parser)
|
511
|
+
end
|
512
|
+
|
513
|
+
def pre_build #:nodoc:
|
514
|
+
self.Filter ||= BuildData.new
|
515
|
+
self.Filter.Name ||= :"Adobe.PPKLite"
|
516
|
+
self.Filter.R ||= 0x20020
|
517
|
+
self.Filter.V ||= 2
|
518
|
+
self.Filter.Date ||= Time.now.to_s
|
519
|
+
|
520
|
+
self.PubSec ||= BuildData.new
|
521
|
+
self.PubSec.NonEFontNoWarn ||= true
|
522
|
+
self.PubSec.Date ||= Time.now.to_s
|
523
|
+
self.PubSec.R ||= 0x20021
|
524
|
+
|
525
|
+
self.App ||= AppData.new
|
526
|
+
self.App.Name ||= :Reader
|
527
|
+
self.App.REx = "11.0.8"
|
528
|
+
self.App.TrustedMode ||= true
|
529
|
+
self.App.OS ||= [ :Win ]
|
530
|
+
self.App.R ||= 0xb0008
|
531
|
+
|
532
|
+
super
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
#
|
537
|
+
# Class representing a digital signature.
|
538
|
+
#
|
539
|
+
class DigitalSignature < Dictionary
|
540
|
+
include StandardObject
|
541
|
+
|
542
|
+
add_type_signature :Filter => :"Adobe.PPKLite"
|
543
|
+
add_type_signature :Filter => :"Adobe.PPKMS"
|
544
|
+
|
545
|
+
field :Type, :Type => Name, :Default => :Sig
|
546
|
+
field :Filter, :Type => Name, :Default => :"Adobe.PPKLite", :Required => true
|
547
|
+
field :SubFilter, :Type => Name
|
548
|
+
field :Contents, :Type => String, :Required => true
|
549
|
+
field :Cert, :Type => [ String, Array.of(String) ]
|
550
|
+
field :ByteRange, :Type => Array.of(Integer, length: 4)
|
551
|
+
field :Reference, :Type => Array.of(Reference), :Version => "1.5"
|
552
|
+
field :Changes, :Type => Array
|
553
|
+
field :Name, :Type => String
|
554
|
+
field :M, :Type => String
|
555
|
+
field :Location, :Type => String
|
556
|
+
field :Reason, :Type => String
|
557
|
+
field :ContactInfo, :Type => String
|
558
|
+
field :R, :Type => Integer
|
559
|
+
field :V, :Type => Integer, :Default => 0, :Version => "1.5"
|
560
|
+
field :Prop_Build, :Type => BuildProperties, :Version => "1.5"
|
561
|
+
field :Prop_AuthTime, :Type => Integer, :Version => "1.5"
|
562
|
+
field :Prop_AuthType, :Type => Name, :Version => "1.5"
|
563
|
+
|
564
|
+
def pre_build #:nodoc:
|
565
|
+
self.M = Origami::Date.now
|
566
|
+
self.Prop_Build ||= BuildProperties.new.pre_build
|
567
|
+
|
568
|
+
super
|
569
|
+
end
|
570
|
+
|
571
|
+
def to_s(indent: 1, tab: "\t", eol: $/) #:nodoc:
|
572
|
+
|
573
|
+
# Must be deterministic.
|
574
|
+
indent, tab, eol = 1, "\t", $/
|
575
|
+
|
576
|
+
content = TOKENS.first + eol
|
577
|
+
|
578
|
+
self.to_a.sort_by{ |key, _| key }.reverse_each do |key, value|
|
579
|
+
content << tab * indent << key.to_s << " "
|
580
|
+
content << (value.is_a?(Dictionary) ? value.to_s(indent: indent + 1) : value.to_s) << eol
|
581
|
+
end
|
582
|
+
|
583
|
+
content << tab * (indent - 1) << TOKENS.last
|
584
|
+
|
585
|
+
output(content)
|
586
|
+
end
|
587
|
+
|
588
|
+
def ranges
|
589
|
+
byte_range = self.ByteRange
|
590
|
+
|
591
|
+
unless byte_range.is_a?(Array) and byte_range.length == 4 and byte_range.all? {|i| i.is_a?(Integer) }
|
592
|
+
raise SignatureError, "Invalid ByteRange field value"
|
593
|
+
end
|
594
|
+
|
595
|
+
byte_range.map(&:to_i).each_slice(2).map do |start, length|
|
596
|
+
(start...start + length)
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
def signature_data
|
601
|
+
raise SignatureError, "Invalid signature data" unless self[:Contents].is_a?(String)
|
602
|
+
|
603
|
+
self[:Contents]
|
604
|
+
end
|
605
|
+
|
606
|
+
def certificate_chain
|
607
|
+
return [] unless key?(:Cert)
|
608
|
+
|
609
|
+
chain = self.Cert
|
610
|
+
unless chain.is_a?(String) or (chain.is_a?(Array) and chain.all?{|cert| cert.is_a?(String)})
|
611
|
+
return SignatureError, "Invalid embedded certificate chain"
|
612
|
+
end
|
613
|
+
|
614
|
+
[ chain ].flatten.map! {|str| OpenSSL::X509::Certificate.new(str) }
|
615
|
+
end
|
616
|
+
|
617
|
+
def signature_offset #:nodoc:
|
618
|
+
indent, tab, eol = 1, "\t", $/
|
619
|
+
content = "#{no} #{generation} obj" + eol + TOKENS.first + eol
|
620
|
+
|
621
|
+
self.to_a.sort_by{ |key, _| key }.reverse_each do |key, value|
|
622
|
+
if key == :Contents
|
623
|
+
content << tab * indent + key.to_s + " "
|
624
|
+
|
625
|
+
return content.size
|
626
|
+
else
|
627
|
+
content << tab * indent + key.to_s << " "
|
628
|
+
content << (value.is_a?(Dictionary) ? value.to_s(indent: indent + 1) : value.to_s) << eol
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
nil
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
end
|
637
|
+
|
638
|
+
module UsageRights
|
639
|
+
|
640
|
+
module Rights
|
641
|
+
DOCUMENT_FULLSAVE = %i[Document FullSave]
|
642
|
+
DOCUMENT_ALL = DOCUMENT_FULLSAVE
|
643
|
+
|
644
|
+
ANNOTS_CREATE = %i[Annots Create]
|
645
|
+
ANNOTS_DELETE = %i[Annots Delete]
|
646
|
+
ANNOTS_MODIFY = %i[Annots Modify]
|
647
|
+
ANNOTS_COPY = %i[Annots Copy]
|
648
|
+
ANNOTS_IMPORT = %i[Annots Import]
|
649
|
+
ANNOTS_EXPORT = %i[Annots Export]
|
650
|
+
ANNOTS_ONLINE = %i[Annots Online]
|
651
|
+
ANNOTS_SUMMARYVIEW = %i[Annots SummaryView]
|
652
|
+
ANNOTS_ALL = %i[Annots Create Modify Copy Import Export Online SummaryView]
|
653
|
+
|
654
|
+
FORM_FILLIN = %i[Form FillIn]
|
655
|
+
FORM_IMPORT = %i[Form Import]
|
656
|
+
FORM_EXPORT = %i[Form Export]
|
657
|
+
FORM_SUBMITSTANDALONE = %i[Form SubmitStandAlone]
|
658
|
+
FORM_SPAWNTEMPLATE = %i[Form SpawnTemplate]
|
659
|
+
FORM_BARCODEPLAINTEXT = %i[Form BarcodePlaintext]
|
660
|
+
FORM_ONLINE = %i[Form Online]
|
661
|
+
FORM_ALL = %i[Form FillIn Import Export SubmitStandAlone SpawnTemplate BarcodePlaintext Online]
|
662
|
+
|
663
|
+
FORMEX_BARCODEPLAINTEXT = %i[FormEx BarcodePlaintext]
|
664
|
+
FORMEX_ALL = FORMEX_BARCODEPLAINTEXT
|
665
|
+
|
666
|
+
SIGNATURE_MODIFY = %i[Signature Modify]
|
667
|
+
SIGNATURE_ALL = SIGNATURE_MODIFY
|
668
|
+
|
669
|
+
EF_CREATE = %i[EF Create]
|
670
|
+
EF_DELETE = %i[EF Delete]
|
671
|
+
EF_MODIFY = %i[EF Modify]
|
672
|
+
EF_IMPORT = %i[EF Import]
|
673
|
+
EF_ALL = %i[EF Create Delete Modify Import]
|
674
|
+
|
675
|
+
ALL = [ DOCUMENT_ALL, ANNOTS_ALL, FORM_ALL, SIGNATURE_ALL, EF_ALL ]
|
676
|
+
end
|
677
|
+
|
678
|
+
class TransformParams < Dictionary
|
679
|
+
include StandardObject
|
680
|
+
|
681
|
+
VERSION = Name.new("2.2")
|
682
|
+
|
683
|
+
field :Type, :Type => Name, :Default => :TransformParams
|
684
|
+
field :Document, :Type => Array.of(Name)
|
685
|
+
field :Msg, :Type => String
|
686
|
+
field :V, :Type => Name, :Default => VERSION
|
687
|
+
field :Annots, :Type => Array.of(Name)
|
688
|
+
field :Form, :Type => Array.of(Name)
|
689
|
+
field :FormEx, :Type => Array.of(Name)
|
690
|
+
field :Signature, :Type => Array.of(Name)
|
691
|
+
field :EF, :Type => Array.of(Name), :Version => "1.6"
|
692
|
+
field :P, :Type => Boolean, :Default => false, :Version => "1.6"
|
693
|
+
|
694
|
+
def initialize(hash = {}, parser = nil)
|
695
|
+
set_indirect(false)
|
696
|
+
|
697
|
+
super(hash, parser)
|
698
|
+
end
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
end
|