Skip to content

Commit

Permalink
Adds a005 signature support
Browse files Browse the repository at this point in the history
  • Loading branch information
jplot committed Aug 28, 2024
1 parent de5c9cf commit 1fcb434
Show file tree
Hide file tree
Showing 14 changed files with 127 additions and 74 deletions.
6 changes: 5 additions & 1 deletion lib/epics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,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/middleware/xmlsig"
Expand Down
31 changes: 23 additions & 8 deletions lib/epics/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ 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

def_delegators :connection, :post

VERSION_H3 = 'H003'
VERSION_H4 = 'H004'
VERSION_A5 = 'A005'
VERSION_A6 = 'A006'

VERSIONS = [VERSION_H3, VERSION_H4]

Expand All @@ -24,6 +27,7 @@ def initialize(keys_content, passphrase, url, host_id, user_id, partner_id)
self.partner_id = partner_id
self.current_order_id = 0
self.version = VERSION_H4
self.signature_version = VERSION_A6

yield self if block_given?
end
Expand Down Expand Up @@ -63,10 +67,6 @@ def encryption_key
keys[encryption_version]
end

def signature_version
'A006'
end

def signature_key
keys[signature_version]
end
Expand Down Expand Up @@ -106,7 +106,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
Expand Down Expand Up @@ -159,7 +164,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]
Expand Down Expand Up @@ -319,7 +329,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
Expand All @@ -329,7 +339,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

Expand Down
8 changes: 3 additions & 5 deletions lib/epics/generic_upload_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 2 additions & 7 deletions lib/epics/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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?
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions lib/epics/signature_algorithm.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Epics::SignatureAlgorithm
end
28 changes: 10 additions & 18 deletions lib/epics/key.rb → lib/epics/signature_algorithm/base.rb
Original file line number Diff line number Diff line change
@@ -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

###
Expand All @@ -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
42 changes: 42 additions & 0 deletions lib/epics/signature_algorithm/rsa.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions lib/epics/signature_algorithm/rsapkcs1.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Epics::SignatureAlgorithm::RsaPkcs1 < Epics::SignatureAlgorithm::Rsa
end
24 changes: 24 additions & 0 deletions lib/epics/signature_algorithm/rsapss.rb
Original file line number Diff line number Diff line change
@@ -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
8 changes: 2 additions & 6 deletions lib/epics/signer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
14 changes: 7 additions & 7 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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"] }
Expand Down
10 changes: 1 addition & 9 deletions spec/generic_upload_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 3 additions & 12 deletions spec/key_spec.rb
Original file line number Diff line number Diff line change
@@ -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'))) }

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/signer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 1fcb434

Please sign in to comment.