lib/origami/signature.rb in origami-2.0.0 vs lib/origami/signature.rb in origami-2.0.1
- old
+ new
@@ -16,16 +16,11 @@
You should have received a copy of the GNU Lesser General Public License
along with Origami. If not, see <http://www.gnu.org/licenses/>.
=end
-begin
- require 'openssl' if Origami::OPTIONS[:use_openssl]
-rescue LoadError
- Origami::OPTIONS[:use_openssl] = false
-end
-
+require 'openssl'
require 'digest/sha1'
module Origami
class PDF
@@ -36,53 +31,34 @@
# Verify a document signature.
# _:trusted_certs_: an array of trusted X509 certificates.
# If no argument is passed, embedded certificates are treated as trusted.
#
def verify(trusted_certs: [])
- unless Origami::OPTIONS[:use_openssl]
- fail "OpenSSL is not present or has been disabled."
- end
-
digsig = self.signature
+ digsig = digsig.cast_to(Signature::DigitalSignature) unless digsig.is_a?(Signature::DigitalSignature)
unless digsig[:Contents].is_a?(String)
raise SignatureError, "Invalid digital signature contents"
end
store = OpenSSL::X509::Store.new
- trusted_certs.each do |ca| store.add_cert(ca) end
+ trusted_certs.each { |ca| store.add_cert(ca) }
flags = 0
flags |= OpenSSL::PKCS7::NOVERIFY if trusted_certs.empty?
- stream = StringScanner.new(self.original_data)
- stream.pos = digsig[:Contents].file_offset
- Object.typeof(stream).parse(stream)
- endofsig_offset = stream.pos
- stream.terminate
+ data = extract_signed_data(digsig)
+ signature = digsig[:Contents]
+ subfilter = digsig.SubFilter.value
- s1,l1,s2,l2 = digsig.ByteRange
- if s1.value != 0 or
- (s2.value + l2.value) != self.original_data.size or
- (s1.value + l1.value) != digsig[:Contents].file_offset or
- s2.value != endofsig_offset
+ case subfilter
+ when Signature::DigitalSignature::PKCS7_DETACHED
+ Signature.verify_pkcs7_detached_signature(data, signature, store, flags)
- raise SignatureError, "Invalid signature byte range"
- end
- data = self.original_data[s1,l1] + self.original_data[s2,l2]
+ when Signature::DigitalSignature::PKCS7_SHA1
+ Signature.verify_pkcs7_sha1_signature(data, signature, store, flags)
- case digsig.SubFilter.value.to_s
- when 'adbe.pkcs7.detached'
- flags |= OpenSSL::PKCS7::DETACHED
- p7 = OpenSSL::PKCS7.new(digsig[:Contents].value)
- raise SignatureError, "Not a PKCS7 detached signature" unless p7.detached?
- p7.verify([], store, data, flags)
-
- when 'adbe.pkcs7.sha1'
- p7 = OpenSSL::PKCS7.new(digsig[:Contents].value)
- p7.verify([], store, nil, flags) and p7.data == Digest::SHA1.digest(data)
-
else
raise NotImplementedError, "Unsupported method #{digsig.SubFilter}"
end
end
@@ -105,14 +81,10 @@
issuer: nil,
location: nil,
contact: nil,
reason: nil)
- unless Origami::OPTIONS[:use_openssl]
- fail "OpenSSL is not present or has been disabled."
- end
-
unless certificate.is_a?(OpenSSL::X509::Certificate)
raise TypeError, "A OpenSSL::X509::Certificate object must be passed."
end
unless key.is_a?(OpenSSL::PKey::RSA)
@@ -138,22 +110,22 @@
OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY
).to_der.size
end
when 'adbe.pkcs7.sha1'
- signfield_size = -> (crt, pkey, certs) do
+ signfield_size = -> (crt, pkey, certs) do
OpenSSL::PKCS7.sign(
crt,
pkey,
Digest::SHA1.digest(''),
certs,
OpenSSL::PKCS7::BINARY
).to_der.size
end
when 'adbe.x509.rsa_sha1'
- signfield_size = -> (crt, pkey, certs) do
+ signfield_size = -> (_crt, pkey, _certs) do
pkey.private_encrypt(
Digest::SHA1.digest('')
).size
end
raise NotImplementedError, "Unsupported method #{method.inspect}"
@@ -170,11 +142,11 @@
end
annotation.V = digsig
add_fields(annotation)
self.Catalog.AcroForm.SigFlags =
- InteractiveForm::SigFlags::SIGNATURESEXIST | InteractiveForm::SigFlags::APPENDONLY
+ InteractiveForm::SigFlags::SIGNATURES_EXIST | InteractiveForm::SigFlags::APPEND_ONLY
digsig.Type = :Sig #:nodoc:
digsig.Contents = HexaString.new("\x00" * signfield_size[certificate, key, ca]) #:nodoc:
digsig.Filter = :"Adobe.PPKLite" #:nodoc:
digsig.SubFilter = Name.new(method) #:nodoc:
@@ -261,25 +233,22 @@
# Returns whether the document contains a digital signature.
#
def signed?
begin
self.Catalog.AcroForm.is_a?(Dictionary) and
- self.Catalog.AcroForm.has_key?(:SigFlags) and
- (self.Catalog.AcroForm.SigFlags & InteractiveForm::SigFlags::SIGNATURESEXIST != 0)
+ self.Catalog.AcroForm.SigFlags.is_a?(Integer) and
+ (self.Catalog.AcroForm.SigFlags & InteractiveForm::SigFlags::SIGNATURES_EXIST != 0)
rescue InvalidReferenceError
false
end
end
#
# Enable the document Usage Rights.
# _rights_:: list of rights defined in UsageRights::Rights
#
def enable_usage_rights(cert, pkey, *rights)
- unless Origami::OPTIONS[:use_openssl]
- fail "OpenSSL is not present or has been disabled."
- end
signfield_size = -> (crt, key, ca) do
OpenSSL::PKCS7.sign(
crt,
key,
@@ -299,11 +268,11 @@
# Forge digital signature dictionary
#
digsig = Signature::DigitalSignature.new.set_indirect(true)
self.Catalog.AcroForm ||= InteractiveForm.new
- #self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::APPENDONLY
+ #self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::APPEND_ONLY
digsig.Type = :Sig #:nodoc:
digsig.Contents = HexaString.new("\x00" * signfield_size[certificate, key, []]) #:nodoc:
digsig.Filter = :"Adobe.PPKLite" #:nodoc:
digsig.Name = "ARE Acrobat Product v8.0 P23 0002337" #:nodoc:
@@ -388,10 +357,38 @@
return field.V if field.FT == :Sig and field.V.is_a?(Dictionary)
end
raise SignatureError, "Cannot find digital signature"
end
+
+ private
+
+ #
+ # Verifies the ByteRange field of a digital signature and returned the signed data.
+ #
+ def extract_signed_data(digsig)
+ # Computes the boundaries of the Contents field.
+ start_sig = digsig[:Contents].file_offset
+
+ stream = StringScanner.new(self.original_data)
+ stream.pos = digsig[:Contents].file_offset
+ Object.typeof(stream).parse(stream)
+ end_sig = stream.pos
+ stream.terminate
+
+ r1, r2 = digsig.ranges
+ if r1.begin != 0 or
+ r2.end != self.original_data.size or
+ r1.end != start_sig or
+ r2.begin != end_sig
+
+ raise SignatureError, "Invalid signature byte range"
+ end
+
+ self.original_data[r1] + self.original_data[r2]
+ end
+
end
class Perms < Dictionary
include StandardObject
@@ -400,10 +397,25 @@
field :UR3, :Type => Dictionary, :Version => "1.6"
end
module Signature
+ # Verifies a PKCS7 detached signature.
+ def self.verify_pkcs7_detached_signature(data, signature, store, flags)
+ pkcs7 = OpenSSL::PKCS7.new(signature)
+ raise SignatureError, "Not a PKCS7 detached signature" unless pkcs7.detached?
+
+ flags |= OpenSSL::PKCS7::DETACHED
+ pkcs7.verify([], store, data, flags)
+ end
+
+ # Verifies a PKCS7-SHA1 signature.
+ def self.verify_pkcs7_sha1_signature(data, signature, store, flags)
+ pkcs7 = OpenSSL::PKCS7.new(signature)
+ pkcs7.verify([], store, nil, flags) and pkcs7.data == Digest::SHA1.digest(data)
+ end
+
#
# Class representing a signature which can be embedded in DigitalSignature dictionary.
# It must be a direct object.
#
class Reference < Dictionary
@@ -492,10 +504,14 @@
# Class representing a digital signature.
#
class DigitalSignature < Dictionary
include StandardObject
+ PKCS1_RSA_SHA1 = :"adbe.x509.rsa_sha1"
+ PKCS7_SHA1 = :"adbe.pkcs7.sha1"
+ PKCS7_DETACHED = :"adbe.pkcs7.detached"
+
field :Type, :Type => Name, :Default => :Sig
field :Filter, :Type => Name, :Default => :"Adobe.PPKLite", :Required => true
field :SubFilter, :Type => Name
field :Contents, :Type => String, :Required => true
field :Cert, :Type => [ String, Array.of(String) ]
@@ -533,9 +549,21 @@
end
content << tab * (indent - 1) << TOKENS.last
output(content)
+ end
+
+ def ranges
+ byte_range = self.ByteRange
+
+ unless byte_range.is_a?(Array) and byte_range.length == 4 and byte_range.all? {|i| i.is_a?(Integer) }
+ raise SignatureError, "Invalid ByteRange field value"
+ end
+
+ byte_range.map(&:to_i).each_slice(2).map do |start, length|
+ (start...start + length)
+ end
end
def signature_offset #:nodoc:
indent, tab = 1, "\t"
content = "#{no} #{generation} obj" + EOL + TOKENS.first + EOL