Skip to content

Commit

Permalink
signature: support for adbe.x509.rsa_sha1
Browse files Browse the repository at this point in the history
  • Loading branch information
Guillaume Delugré committed Jan 18, 2019
1 parent 3833beb commit c4e5d66
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 39 deletions.
87 changes: 78 additions & 9 deletions lib/origami/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def verify(trusted_certs: [],
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
signature = digsig.signature_data
chain = digsig.certificate_chain
subfilter = digsig.SubFilter.value

store = OpenSSL::X509::Store.new
store.set_default_paths if use_system_store
Expand All @@ -64,10 +64,7 @@ def verify(trusted_certs: [],
}

data = extract_signed_data(digsig)
signature = digsig[:Contents]
subfilter = digsig.SubFilter.value

Signature.verify(subfilter.to_s, data, signature, store)
Signature.verify(subfilter.to_s, data, signature, store, chain)
end

#
Expand Down Expand Up @@ -344,7 +341,55 @@ module Signature
PKCS7_SHA1 = "adbe.pkcs7.sha1"
PKCS7_DETACHED = "adbe.pkcs7.detached"

def self.verify(method, data, signature, store)
#
# PKCS1 class used for adbe.x509.rsa_sha1.
#
class PKCS1
class PKCS1Error < SignatureError; end

def initialize(signature)
@signature_object = decode_pkcs1(signature)
end

def verify(certificate, chain, store, data)
store.verify(certificate, chain) and certificate.public_key.verify(OpenSSL::Digest::SHA1.new, @signature_object.value, data)
end

def self.sign(certificate, key, data)
raise PKCS1Error, "Invalid key for certificate" unless certificate.check_private_key(key)

self.new encode_pkcs1 key.sign(OpenSSL::Digest::SHA1.new, data)
end

def to_der
@signature_object.to_der
end

private

def decode_pkcs1(data)
#
# Extracts the first ASN.1 object from the data and discards the rest.
# Must be an octet string.
#
signature_len = 0
OpenSSL::ASN1.traverse(data) do |_, offset, hdr_len, len, _, _, tag|
raise PKCS1Error, "Invalid PKCS1 object, expected an ASN.1 octet string" unless tag == OpenSSL::ASN1::OCTET_STRING

signature_len = offset + hdr_len + len
break
end

OpenSSL::ASN1.decode(data[0, signature_len])
end

def self.encode_pkcs1(data)
OpenSSL::ASN1::OctetString.new(data).to_der
end
private_class_method :encode_pkcs1
end

def self.verify(method, data, signature, store, chain)
case method
when PKCS7_DETACHED
pkcs7 = OpenSSL::PKCS7.new(signature)
Expand All @@ -355,6 +400,12 @@ def self.verify(method, data, signature, store)
pkcs7 = OpenSSL::PKCS7.new(signature)
pkcs7.verify([], store, nil, OpenSSL::PKCS7::BINARY) and pkcs7.data == Digest::SHA1.digest(data)

when PKCS1_RSA_SHA1
raise SignatureError, "Cannot verify RSA signature without a certificate" if chain.empty?
cert = chain.shift
pkcs1 = PKCS1.new(signature)
pkcs1.verify(cert, chain, store, data)

else
raise NotImplementedError, "Unsupported signature method #{method.inspect}"
end
Expand All @@ -379,7 +430,8 @@ def self.compute(method, data, certificate, key, ca)
OpenSSL::PKCS7.sign(certificate, key, Digest::SHA1.digest(data), ca, OpenSSL::PKCS7::BINARY).to_der

when PKCS1_RSA_SHA1
key.sign(OpenSSL::Digest::SHA1.new, data)
PKCS1.sign(certificate, key, data).to_der

else
raise NotImplementedError, "Unsupported signature method #{method.inspect}"
end
Expand Down Expand Up @@ -537,6 +589,23 @@ def ranges
end
end

def signature_data
raise SignatureError, "Invalid signature data" unless self[:Contents].is_a?(String)

self[:Contents]
end

def certificate_chain
return [] unless key?(:Cert)

chain = self.Cert
unless chain.is_a?(String) or (chain.is_a?(Array) and chain.all?{|cert| cert.is_a?(String)})
return SignatureError, "Invalid embedded certificate chain"
end

[ chain ].flatten.map! {|str| OpenSSL::X509::Certificate.new(str) }
end

def signature_offset #:nodoc:
indent, tab, eol = 1, "\t", $/
content = "#{no} #{generation} obj" + eol + TOKENS.first + eol
Expand Down
40 changes: 10 additions & 30 deletions test/test_pdf_sign.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ def setup_document_with_annotation
[ document, annotation ]
end

def test_sign_pkcs7_sha1
def sign_document_with_method(method)
document, annotation = setup_document_with_annotation

document.sign(@cert, @key,
method: Signature::PKCS7_SHA1,
method: method,
annotation: annotation,
issuer: "Guillaume Delugré",
location: "France",
Expand Down Expand Up @@ -83,35 +83,15 @@ def test_sign_pkcs7_sha1
assert result
end

def test_sign_pkcs7_detached
document, annotation = setup_document_with_annotation

document.sign(@cert, @key,
method: Signature::PKCS7_DETACHED,
annotation: annotation,
issuer: "Guillaume Delugré",
location: "France",
contact: "origami@localhost",
reason: "Example"
)

assert document.frozen?
assert document.signed?

output = StringIO.new
document.save(output)

document = PDF.read(output.reopen(output.string,'r'), verbosity: Parser::VERBOSE_QUIET)

refute document.verify
assert document.verify(allow_self_signed: true)
assert document.verify(trusted_certs: [@cert])
refute document.verify(trusted_certs: [@other_cert])
def test_sign_pkcs7_sha1
sign_document_with_method(Signature::PKCS7_SHA1)
end

result = document.verify do |ctx|
ctx.error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && ctx.current_cert.to_pem == @cert.to_pem
end
def test_sign_pkcs7_detached
sign_document_with_method(Signature::PKCS7_DETACHED)
end

assert result
def test_sign_x509_sha1
sign_document_with_method(Signature::PKCS1_RSA_SHA1)
end
end

0 comments on commit c4e5d66

Please sign in to comment.