diff --git a/lib/epics.rb b/lib/epics.rb index 2376cd5..00f33cc 100644 --- a/lib/epics.rb +++ b/lib/epics.rb @@ -10,7 +10,11 @@ require 'securerandom' require 'time' require "epics/version" -require "epics/key" +require "epics/signature_algorithm" +require "epics/signature_algorithm/base" +require "epics/signature_algorithm/rsa" +require "epics/signature_algorithm/rsapss" +require "epics/signature_algorithm/rsapkcs1" require "epics/response" require "epics/error" require 'epics/letter_renderer' diff --git a/lib/epics/client.rb b/lib/epics/client.rb index 43f84cf..78dd5e6 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -3,6 +3,7 @@ class Epics::Client attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :keys, :keys_content, :current_order_id attr_reader :version + attr_accessor :signature_version attr_writer :iban, :bic, :name attr_accessor :locale @@ -10,6 +11,8 @@ class Epics::Client VERSION_H3 = 'H003' VERSION_H4 = 'H004' + VERSION_A5 = 'A005' + VERSION_A6 = 'A006' VERSIONS = [VERSION_H3, VERSION_H4] @@ -26,6 +29,7 @@ def initialize(keys_content, passphrase, url, host_id, user_id, partner_id) self.locale = :de self.current_order_id = 0 self.version = VERSION_H4 + self.signature_version = VERSION_A6 yield self if block_given? end @@ -65,10 +69,6 @@ def encryption_key keys[encryption_version] end - def signature_version - 'A006' - end - def signature_key keys[signature_version] end @@ -108,7 +108,12 @@ def order_types def self.setup(passphrase, url, host_id, user_id, partner_id, keysize = 2048, &block) client = new(nil, passphrase, url, host_id, user_id, partner_id, &block) client.keys = [client.signature_version, client.authentication_version, client.encryption_version].each_with_object({}) do |type, memo| - memo[type] = Epics::Key.new( OpenSSL::PKey::RSA.generate(keysize) ) + memo[type] = case type + when VERSION_A6 + Epics::SignatureAlgorithm::RsaPss.new( OpenSSL::PKey::RSA.generate(keysize) ) + else + Epics::SignatureAlgorithm::RsaPkcs1.new( OpenSSL::PKey::RSA.generate(keysize) ) + end end client @@ -164,7 +169,12 @@ def HPB bank = OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der) - self.keys["#{host_id.upcase}.#{type}"] = Epics::Key.new(bank) + self.keys["#{host_id.upcase}.#{type}"] = case type + when VERSION_A6 + Epics::SignatureAlgorithm::RsaPss.new(bank) + else + Epics::SignatureAlgorithm::RsaPkcs1.new(bank) + end end [bank_authentication_key, bank_encryption_key] @@ -324,7 +334,7 @@ def download_and_unzip(order_type, *args, **options) end def connection - @connection ||= Faraday.new(headers: { 'Content-Type' => 'text/xml', user_agent: USER_AGENT}, ssl: { verify: verify_ssl? }) do |faraday| + @connection ||= Faraday.new(headers: { 'Content-Type' => 'text/xml', user_agent: USER_AGENT }, ssl: { verify: verify_ssl? }) do |faraday| faraday.use Epics::XMLSIG, { client: self } faraday.use Epics::ParseEbics, { client: self} # faraday.use MyAdapter @@ -334,7 +344,12 @@ def connection def extract_keys JSON.load(self.keys_content).each_with_object({}) do |(type, key), memo| - memo[type] = Epics::Key.new(decrypt(key)) if key + memo[type] = case type + when VERSION_A6 + Epics::SignatureAlgorithm::RsaPss.new(decrypt(key)) + else + Epics::SignatureAlgorithm::RsaPkcs1.new(decrypt(key)) + end if key end end diff --git a/lib/epics/generic_upload_request.rb b/lib/epics/generic_upload_request.rb index 58f6e43..f7195e2 100644 --- a/lib/epics/generic_upload_request.rb +++ b/lib/epics/generic_upload_request.rb @@ -14,10 +14,6 @@ def cipher @cipher ||= OpenSSL::Cipher.new("aes-128-cbc").tap { |cipher| cipher.encrypt } end - def digester - @digester ||= OpenSSL::Digest::SHA256.new - end - def body Nokogiri::XML::Builder.new do |xml| xml.body { @@ -46,7 +42,9 @@ def order_signature end def signature_value - client.signature_key.sign( digester.digest(document.gsub(/\n|\r/, "")) ) + Base64.encode64( + client.signature_key.sign(client.signature_key.digester.digest(document.gsub(/\n|\r/, ""))) + ).gsub("\n", '') end def encrypt(d) diff --git a/lib/epics/response.rb b/lib/epics/response.rb index 367e003..03066c7 100644 --- a/lib/epics/response.rb +++ b/lib/epics/response.rb @@ -57,7 +57,7 @@ def digest_valid? authenticated = doc.xpath("//*[@authenticate='true']").map(&:canonicalize).join digest_value = doc.xpath("//ds:DigestValue", ds: "http://www.w3.org/2000/09/xmldsig#").first - digest = Base64.encode64(digester.digest(authenticated)).strip + digest = Base64.encode64(client.signature_key.digester.digest(authenticated)).strip digest == digest_value.content end @@ -66,7 +66,7 @@ def signature_valid? signature = doc.xpath("//ds:SignedInfo", ds: "http://www.w3.org/2000/09/xmldsig#").first.canonicalize signature_value = doc.xpath("//ds:SignatureValue", ds: "http://www.w3.org/2000/09/xmldsig#").first - client.bank_authentication_key.key.verify(digester, Base64.decode64(signature_value.content), signature) + client.bank_authentication_key.verify(signature_value.content, signature) end def public_digest_valid? @@ -97,9 +97,4 @@ def transaction_key @transaction_key ||= client.encryption_key.key.private_decrypt(transaction_key_encrypted) end - - def digester - @digester ||= OpenSSL::Digest::SHA256.new - end - end diff --git a/lib/epics/signature_algorithm.rb b/lib/epics/signature_algorithm.rb new file mode 100644 index 0000000..c79292c --- /dev/null +++ b/lib/epics/signature_algorithm.rb @@ -0,0 +1,2 @@ +module Epics::SignatureAlgorithm +end \ No newline at end of file diff --git a/lib/epics/key.rb b/lib/epics/signature_algorithm/base.rb similarity index 53% rename from lib/epics/key.rb rename to lib/epics/signature_algorithm/base.rb index a8d0f4e..9bdb62e 100644 --- a/lib/epics/key.rb +++ b/lib/epics/signature_algorithm/base.rb @@ -1,12 +1,8 @@ -class Epics::Key +class Epics::SignatureAlgorithm::Base attr_accessor :key def initialize(encoded_key, passphrase = nil) - if encoded_key.kind_of?(OpenSSL::PKey::RSA) - self.key = encoded_key - else - self.key = OpenSSL::PKey::RSA.new(encoded_key) - end + self.key = encoded_key end ### @@ -22,26 +18,22 @@ def public_digest end def n - self.key.n.to_s(16) + raise NotImplementedError end def e - self.key.e.to_s(16) + raise NotImplementedError end def sign(msg) - Base64.encode64( - key.sign_pss( - 'SHA256', - msg, - salt_length: :digest, - mgf1_hash: 'SHA256', - ), - ).gsub("\n", '') + raise NotImplementedError end - def digester - @digester ||= OpenSSL::Digest::SHA256.new + def verify(signature, msg) + raise NotImplementedError end + def digester + raise NotImplementedError + end end diff --git a/lib/epics/signature_algorithm/rsa.rb b/lib/epics/signature_algorithm/rsa.rb new file mode 100644 index 0000000..e48d4ae --- /dev/null +++ b/lib/epics/signature_algorithm/rsa.rb @@ -0,0 +1,42 @@ +class Epics::SignatureAlgorithm::Rsa < Epics::SignatureAlgorithm::Base + HASH_ALGORITHM = 'SHA256' + + def initialize(encoded_key, passphrase = nil) + if encoded_key.kind_of?(OpenSSL::PKey::RSA) + self.key = encoded_key + else + self.key = OpenSSL::PKey::RSA.new(encoded_key) + end + end + + def n + self.key.n.to_s(16) + end + + def e + self.key.e.to_s(16) + end + + def sign(msg) + key.sign( + hash_algorithm, + msg + ) + end + + def verify(signature, msg) + key.verify( + hash_algorithm, + Base64.decode64(signature), + msg + ) + end + + def hash_algorithm + HASH_ALGORITHM + end + + def digester + @digester ||= OpenSSL::Digest::SHA256.new + end +end diff --git a/lib/epics/signature_algorithm/rsapkcs1.rb b/lib/epics/signature_algorithm/rsapkcs1.rb new file mode 100644 index 0000000..85c4850 --- /dev/null +++ b/lib/epics/signature_algorithm/rsapkcs1.rb @@ -0,0 +1,2 @@ +class Epics::SignatureAlgorithm::RsaPkcs1 < Epics::SignatureAlgorithm::Rsa +end diff --git a/lib/epics/signature_algorithm/rsapss.rb b/lib/epics/signature_algorithm/rsapss.rb new file mode 100644 index 0000000..74e2bf0 --- /dev/null +++ b/lib/epics/signature_algorithm/rsapss.rb @@ -0,0 +1,24 @@ +class Epics::SignatureAlgorithm::RsaPss < Epics::SignatureAlgorithm::Rsa + def sign(msg) + key.sign_pss( + hash_algorithm, + msg, + salt_length: :digest, + mgf1_hash: mgf1_hash_algorithm, + ) + end + + def verify(signature, msg) + key.verify_pss( + hash_algorithm, + Base64.decode64(signature), + msg, + salt_length: :digest, + mgf1_hash: mgf1_hash_algorithm, + ) + end + + def mgf1_hash_algorithm + HASH_ALGORITHM + end +end diff --git a/lib/epics/signer.rb b/lib/epics/signer.rb index a17cfcb..053667d 100644 --- a/lib/epics/signer.rb +++ b/lib/epics/signer.rb @@ -7,7 +7,7 @@ def initialize(client, doc = nil) end def digest! - content_to_digest = Base64.encode64(digester.digest(doc.xpath("//*[@authenticate='true']").map(&:canonicalize).join)).strip + content_to_digest = Base64.encode64(client.authentication_key.digester.digest(doc.xpath("//*[@authenticate='true']").map(&:canonicalize).join)).strip if digest_node digest_node.content = content_to_digest @@ -20,7 +20,7 @@ def sign! signature_value_node = doc.xpath("//ds:SignatureValue").first if signature_node - signature_value_node.content = Base64.encode64(client.authentication_key.key.sign(digester, signature_node.canonicalize)).gsub(/\n/,'') + signature_value_node.content = Base64.encode64(client.authentication_key.sign(signature_node.canonicalize)).gsub(/\n/,'') end doc @@ -33,8 +33,4 @@ def digest_node def signature_node @s ||= doc.xpath("//ds:SignedInfo").first end - - def digester - OpenSSL::Digest::SHA256.new - end end diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 0d30f4f..22b5e37 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -12,11 +12,11 @@ it 'holds all keys, user and bank' do expect(subject.keys).to match(a_hash_including( - "E002" => be_a(Epics::Key), - "X002" => be_a(Epics::Key), - "A006" => be_a(Epics::Key), - "SIZBN001.E002" => be_a(Epics::Key), - "SIZBN001.X002" => be_a(Epics::Key) + "E002" => be_a(Epics::SignatureAlgorithm::RsaPkcs1), + "X002" => be_a(Epics::SignatureAlgorithm::RsaPkcs1), + "A006" => be_a(Epics::SignatureAlgorithm::RsaPss), + "SIZBN001.E002" => be_a(Epics::SignatureAlgorithm::RsaPkcs1), + "SIZBN001.X002" => be_a(Epics::SignatureAlgorithm::RsaPkcs1) )) end @@ -94,7 +94,7 @@ describe '#HPB' do let(:e_key) do - Epics::Key.new(OpenSSL::PKey::RSA.new(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'bank_e.pem')))) + Epics::SignatureAlgorithm::RsaPss.new(OpenSSL::PKey::RSA.new(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'bank_e.pem')))) end before do @@ -103,7 +103,7 @@ .to_return(status: 200, body: File.read(File.join(File.dirname(__FILE__), 'fixtures', 'xml', 'hpb_response_ebics_ns.xml'))) end - it { expect(subject.HPB).to match([be_a(Epics::Key), be_a(Epics::Key)]) } + it { expect(subject.HPB).to match([be_a(Epics::SignatureAlgorithm::RsaPkcs1), be_a(Epics::SignatureAlgorithm::RsaPkcs1)]) } it 'changes the SIZBN001.(E|X)002 keys' do expect { subject.HPB }.to change { subject.keys["SIZBN001.E002"] } diff --git a/spec/generic_upload_spec.rb b/spec/generic_upload_spec.rb index 63620ca..99b6a23 100644 --- a/spec/generic_upload_spec.rb +++ b/spec/generic_upload_spec.rb @@ -23,15 +23,7 @@ before { allow(OpenSSL::Random).to receive(:random_bytes).with(32).and_return(Base64.strict_decode64("7wtROfiX4tyN60cygJUSsHkhzxX1RVJa8vGNYnflvKc=")) } # digest requires 32 bytes it 'will be the signed document' do - key = subject.client.signature_key.key - - verification_result = key.verify_pss( - 'SHA256', - Base64.decode64(subject.signature_value), - OpenSSL::Digest::SHA256.new.digest(document), - salt_length: :digest, - mgf1_hash: 'SHA256', - ) + verification_result = subject.client.signature_key.verify(subject.signature_value, OpenSSL::Digest::SHA256.new.digest(document)) expect(verification_result).to eq(true) end diff --git a/spec/key_spec.rb b/spec/key_spec.rb index 2fc015c..4b1b20c 100644 --- a/spec/key_spec.rb +++ b/spec/key_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe Epics::Key do +RSpec.describe Epics::SignatureAlgorithm::RsaPss do subject { described_class.new( File.read(File.join( File.dirname(__FILE__), 'fixtures', 'e002.pem'))) } @@ -14,17 +14,8 @@ let(:dsi) { OpenSSL::Digest::SHA256.new.digest("ruby is great") } it 'will generated a digest that can be verified with openssl key.verify_pss' do - signed_digest = subject.sign(dsi) - - key = subject.key - - verification_result = key.verify_pss( - 'SHA256', - Base64.decode64(signed_digest), - dsi, - salt_length: :digest, - mgf1_hash: 'SHA256', - ) + signed_digest = Base64.encode64(subject.sign(dsi)).strip + verification_result = subject.verify(signed_digest, dsi) expect(verification_result).to eq(true) end diff --git a/spec/signer_spec.rb b/spec/signer_spec.rb index 8cc5824..78ca8e9 100644 --- a/spec/signer_spec.rb +++ b/spec/signer_spec.rb @@ -27,7 +27,7 @@ end it 'can be verified with the same key' do - expect(client.authentication_key.key.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(subject.doc.xpath("//ds:SignatureValue").first.content), subject.signature_node.canonicalize)).to be(true) + expect(client.authentication_key.verify(subject.doc.xpath("//ds:SignatureValue").first.content, subject.signature_node.canonicalize)).to be(true) end end