Skip to content

Commit a47eb00

Browse files
committed
Adds a005 signature support
1 parent a57d670 commit a47eb00

14 files changed

+127
-74
lines changed

lib/epics.rb

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
require 'securerandom'
1111
require 'time'
1212
require "epics/version"
13-
require "epics/key"
13+
require "epics/signature_algorithm"
14+
require "epics/signature_algorithm/base"
15+
require "epics/signature_algorithm/rsa"
16+
require "epics/signature_algorithm/rsapss"
17+
require "epics/signature_algorithm/rsapkcs1"
1418
require "epics/response"
1519
require "epics/error"
1620
require 'epics/letter_renderer'

lib/epics/client.rb

+23-8
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ class Epics::Client
33

44
attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :keys, :keys_content, :current_order_id
55
attr_reader :version
6+
attr_accessor :signature_version
67
attr_writer :iban, :bic, :name
78
attr_accessor :locale
89

910
def_delegators :connection, :post
1011

1112
VERSION_H3 = 'H003'
1213
VERSION_H4 = 'H004'
14+
VERSION_A5 = 'A005'
15+
VERSION_A6 = 'A006'
1316

1417
VERSIONS = [VERSION_H3, VERSION_H4]
1518

@@ -26,6 +29,7 @@ def initialize(keys_content, passphrase, url, host_id, user_id, partner_id)
2629
self.locale = :de
2730
self.current_order_id = 0
2831
self.version = VERSION_H4
32+
self.signature_version = VERSION_A6
2933

3034
yield self if block_given?
3135
end
@@ -65,10 +69,6 @@ def encryption_key
6569
keys[encryption_version]
6670
end
6771

68-
def signature_version
69-
'A006'
70-
end
71-
7272
def signature_key
7373
keys[signature_version]
7474
end
@@ -108,7 +108,12 @@ def order_types
108108
def self.setup(passphrase, url, host_id, user_id, partner_id, keysize = 2048, &block)
109109
client = new(nil, passphrase, url, host_id, user_id, partner_id, &block)
110110
client.keys = [client.signature_version, client.authentication_version, client.encryption_version].each_with_object({}) do |type, memo|
111-
memo[type] = Epics::Key.new( OpenSSL::PKey::RSA.generate(keysize) )
111+
memo[type] = case type
112+
when VERSION_A6
113+
Epics::SignatureAlgorithm::RsaPss.new( OpenSSL::PKey::RSA.generate(keysize) )
114+
else
115+
Epics::SignatureAlgorithm::RsaPkcs1.new( OpenSSL::PKey::RSA.generate(keysize) )
116+
end
112117
end
113118

114119
client
@@ -164,7 +169,12 @@ def HPB
164169

165170
bank = OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
166171

167-
self.keys["#{host_id.upcase}.#{type}"] = Epics::Key.new(bank)
172+
self.keys["#{host_id.upcase}.#{type}"] = case type
173+
when VERSION_A6
174+
Epics::SignatureAlgorithm::RsaPss.new(bank)
175+
else
176+
Epics::SignatureAlgorithm::RsaPkcs1.new(bank)
177+
end
168178
end
169179

170180
[bank_authentication_key, bank_encryption_key]
@@ -324,7 +334,7 @@ def download_and_unzip(order_type, *args, **options)
324334
end
325335

326336
def connection
327-
@connection ||= Faraday.new(headers: { 'Content-Type' => 'text/xml', user_agent: USER_AGENT}, ssl: { verify: verify_ssl? }) do |faraday|
337+
@connection ||= Faraday.new(headers: { 'Content-Type' => 'text/xml', user_agent: USER_AGENT }, ssl: { verify: verify_ssl? }) do |faraday|
328338
faraday.use Epics::XMLSIG, { client: self }
329339
faraday.use Epics::ParseEbics, { client: self}
330340
# faraday.use MyAdapter
@@ -334,7 +344,12 @@ def connection
334344

335345
def extract_keys
336346
JSON.load(self.keys_content).each_with_object({}) do |(type, key), memo|
337-
memo[type] = Epics::Key.new(decrypt(key)) if key
347+
memo[type] = case type
348+
when VERSION_A6
349+
Epics::SignatureAlgorithm::RsaPss.new(decrypt(key))
350+
else
351+
Epics::SignatureAlgorithm::RsaPkcs1.new(decrypt(key))
352+
end if key
338353
end
339354
end
340355

lib/epics/generic_upload_request.rb

+3-5
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ def cipher
1414
@cipher ||= OpenSSL::Cipher.new("aes-128-cbc").tap { |cipher| cipher.encrypt }
1515
end
1616

17-
def digester
18-
@digester ||= OpenSSL::Digest::SHA256.new
19-
end
20-
2117
def body
2218
Nokogiri::XML::Builder.new do |xml|
2319
xml.body {
@@ -46,7 +42,9 @@ def order_signature
4642
end
4743

4844
def signature_value
49-
client.signature_key.sign( digester.digest(document.gsub(/\n|\r/, "")) )
45+
Base64.encode64(
46+
client.signature_key.sign(client.signature_key.digester.digest(document.gsub(/\n|\r/, "")))
47+
).gsub("\n", '')
5048
end
5149

5250
def encrypt(d)

lib/epics/response.rb

+2-7
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def digest_valid?
5757
authenticated = doc.xpath("//*[@authenticate='true']").map(&:canonicalize).join
5858
digest_value = doc.xpath("//ds:DigestValue", ds: "http://www.w3.org/2000/09/xmldsig#").first
5959

60-
digest = Base64.encode64(digester.digest(authenticated)).strip
60+
digest = Base64.encode64(client.signature_key.digester.digest(authenticated)).strip
6161

6262
digest == digest_value.content
6363
end
@@ -66,7 +66,7 @@ def signature_valid?
6666
signature = doc.xpath("//ds:SignedInfo", ds: "http://www.w3.org/2000/09/xmldsig#").first.canonicalize
6767
signature_value = doc.xpath("//ds:SignatureValue", ds: "http://www.w3.org/2000/09/xmldsig#").first
6868

69-
client.bank_authentication_key.key.verify(digester, Base64.decode64(signature_value.content), signature)
69+
client.bank_authentication_key.verify(signature_value.content, signature)
7070
end
7171

7272
def public_digest_valid?
@@ -97,9 +97,4 @@ def transaction_key
9797

9898
@transaction_key ||= client.encryption_key.key.private_decrypt(transaction_key_encrypted)
9999
end
100-
101-
def digester
102-
@digester ||= OpenSSL::Digest::SHA256.new
103-
end
104-
105100
end

lib/epics/signature_algorithm.rb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module Epics::SignatureAlgorithm
2+
end
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
class Epics::Key
1+
class Epics::SignatureAlgorithm::Base
22
attr_accessor :key
33

44
def initialize(encoded_key, passphrase = nil)
5-
if encoded_key.kind_of?(OpenSSL::PKey::RSA)
6-
self.key = encoded_key
7-
else
8-
self.key = OpenSSL::PKey::RSA.new(encoded_key)
9-
end
5+
self.key = encoded_key
106
end
117

128
###
@@ -22,26 +18,22 @@ def public_digest
2218
end
2319

2420
def n
25-
self.key.n.to_s(16)
21+
raise NotImplementedError
2622
end
2723

2824
def e
29-
self.key.e.to_s(16)
25+
raise NotImplementedError
3026
end
3127

3228
def sign(msg)
33-
Base64.encode64(
34-
key.sign_pss(
35-
'SHA256',
36-
msg,
37-
salt_length: :digest,
38-
mgf1_hash: 'SHA256',
39-
),
40-
).gsub("\n", '')
29+
raise NotImplementedError
4130
end
4231

43-
def digester
44-
@digester ||= OpenSSL::Digest::SHA256.new
32+
def verify(signature, msg)
33+
raise NotImplementedError
4534
end
4635

36+
def digester
37+
raise NotImplementedError
38+
end
4739
end

lib/epics/signature_algorithm/rsa.rb

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class Epics::SignatureAlgorithm::Rsa < Epics::SignatureAlgorithm::Base
2+
HASH_ALGORITHM = 'SHA256'
3+
4+
def initialize(encoded_key, passphrase = nil)
5+
if encoded_key.kind_of?(OpenSSL::PKey::RSA)
6+
self.key = encoded_key
7+
else
8+
self.key = OpenSSL::PKey::RSA.new(encoded_key)
9+
end
10+
end
11+
12+
def n
13+
self.key.n.to_s(16)
14+
end
15+
16+
def e
17+
self.key.e.to_s(16)
18+
end
19+
20+
def sign(msg)
21+
key.sign(
22+
hash_algorithm,
23+
msg
24+
)
25+
end
26+
27+
def verify(signature, msg)
28+
key.verify(
29+
hash_algorithm,
30+
Base64.decode64(signature),
31+
msg
32+
)
33+
end
34+
35+
def hash_algorithm
36+
HASH_ALGORITHM
37+
end
38+
39+
def digester
40+
@digester ||= OpenSSL::Digest::SHA256.new
41+
end
42+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Epics::SignatureAlgorithm::RsaPkcs1 < Epics::SignatureAlgorithm::Rsa
2+
end
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class Epics::SignatureAlgorithm::RsaPss < Epics::SignatureAlgorithm::Rsa
2+
def sign(msg)
3+
key.sign_pss(
4+
hash_algorithm,
5+
msg,
6+
salt_length: :digest,
7+
mgf1_hash: mgf1_hash_algorithm,
8+
)
9+
end
10+
11+
def verify(signature, msg)
12+
key.verify_pss(
13+
hash_algorithm,
14+
Base64.decode64(signature),
15+
msg,
16+
salt_length: :digest,
17+
mgf1_hash: mgf1_hash_algorithm,
18+
)
19+
end
20+
21+
def mgf1_hash_algorithm
22+
HASH_ALGORITHM
23+
end
24+
end

lib/epics/signer.rb

+2-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def initialize(client, doc = nil)
77
end
88

99
def digest!
10-
content_to_digest = Base64.encode64(digester.digest(doc.xpath("//*[@authenticate='true']").map(&:canonicalize).join)).strip
10+
content_to_digest = Base64.encode64(client.authentication_key.digester.digest(doc.xpath("//*[@authenticate='true']").map(&:canonicalize).join)).strip
1111

1212
if digest_node
1313
digest_node.content = content_to_digest
@@ -20,7 +20,7 @@ def sign!
2020
signature_value_node = doc.xpath("//ds:SignatureValue").first
2121

2222
if signature_node
23-
signature_value_node.content = Base64.encode64(client.authentication_key.key.sign(digester, signature_node.canonicalize)).gsub(/\n/,'')
23+
signature_value_node.content = Base64.encode64(client.authentication_key.sign(signature_node.canonicalize)).gsub(/\n/,'')
2424
end
2525

2626
doc
@@ -33,8 +33,4 @@ def digest_node
3333
def signature_node
3434
@s ||= doc.xpath("//ds:SignedInfo").first
3535
end
36-
37-
def digester
38-
OpenSSL::Digest::SHA256.new
39-
end
4036
end

spec/client_spec.rb

+7-7
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212

1313
it 'holds all keys, user and bank' do
1414
expect(subject.keys).to match(a_hash_including(
15-
"E002" => be_a(Epics::Key),
16-
"X002" => be_a(Epics::Key),
17-
"A006" => be_a(Epics::Key),
18-
"SIZBN001.E002" => be_a(Epics::Key),
19-
"SIZBN001.X002" => be_a(Epics::Key)
15+
"E002" => be_a(Epics::SignatureAlgorithm::RsaPkcs1),
16+
"X002" => be_a(Epics::SignatureAlgorithm::RsaPkcs1),
17+
"A006" => be_a(Epics::SignatureAlgorithm::RsaPss),
18+
"SIZBN001.E002" => be_a(Epics::SignatureAlgorithm::RsaPkcs1),
19+
"SIZBN001.X002" => be_a(Epics::SignatureAlgorithm::RsaPkcs1)
2020
))
2121
end
2222

@@ -94,7 +94,7 @@
9494

9595
describe '#HPB' do
9696
let(:e_key) do
97-
Epics::Key.new(OpenSSL::PKey::RSA.new(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'bank_e.pem'))))
97+
Epics::SignatureAlgorithm::RsaPss.new(OpenSSL::PKey::RSA.new(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'bank_e.pem'))))
9898
end
9999

100100
before do
@@ -103,7 +103,7 @@
103103
.to_return(status: 200, body: File.read(File.join(File.dirname(__FILE__), 'fixtures', 'xml', 'hpb_response_ebics_ns.xml')))
104104
end
105105

106-
it { expect(subject.HPB).to match([be_a(Epics::Key), be_a(Epics::Key)]) }
106+
it { expect(subject.HPB).to match([be_a(Epics::SignatureAlgorithm::RsaPkcs1), be_a(Epics::SignatureAlgorithm::RsaPkcs1)]) }
107107

108108
it 'changes the SIZBN001.(E|X)002 keys' do
109109
expect { subject.HPB }.to change { subject.keys["SIZBN001.E002"] }

spec/generic_upload_spec.rb

+1-9
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,7 @@
2323
before { allow(OpenSSL::Random).to receive(:random_bytes).with(32).and_return(Base64.strict_decode64("7wtROfiX4tyN60cygJUSsHkhzxX1RVJa8vGNYnflvKc=")) } # digest requires 32 bytes
2424

2525
it 'will be the signed document' do
26-
key = subject.client.signature_key.key
27-
28-
verification_result = key.verify_pss(
29-
'SHA256',
30-
Base64.decode64(subject.signature_value),
31-
OpenSSL::Digest::SHA256.new.digest(document),
32-
salt_length: :digest,
33-
mgf1_hash: 'SHA256',
34-
)
26+
verification_result = subject.client.signature_key.verify(subject.signature_value, OpenSSL::Digest::SHA256.new.digest(document))
3527

3628
expect(verification_result).to eq(true)
3729
end

spec/key_spec.rb

+3-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
RSpec.describe Epics::Key do
1+
RSpec.describe Epics::SignatureAlgorithm::RsaPss do
22

33
subject { described_class.new( File.read(File.join( File.dirname(__FILE__), 'fixtures', 'e002.pem'))) }
44

@@ -14,17 +14,8 @@
1414
let(:dsi) { OpenSSL::Digest::SHA256.new.digest("ruby is great") }
1515

1616
it 'will generated a digest that can be verified with openssl key.verify_pss' do
17-
signed_digest = subject.sign(dsi)
18-
19-
key = subject.key
20-
21-
verification_result = key.verify_pss(
22-
'SHA256',
23-
Base64.decode64(signed_digest),
24-
dsi,
25-
salt_length: :digest,
26-
mgf1_hash: 'SHA256',
27-
)
17+
signed_digest = Base64.encode64(subject.sign(dsi)).strip
18+
verification_result = subject.verify(signed_digest, dsi)
2819

2920
expect(verification_result).to eq(true)
3021
end

spec/signer_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
end
2828

2929
it 'can be verified with the same key' do
30-
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)
30+
expect(client.authentication_key.verify(subject.doc.xpath("//ds:SignatureValue").first.content, subject.signature_node.canonicalize)).to be(true)
3131
end
3232
end
3333

0 commit comments

Comments
 (0)