Skip to content

Commit

Permalink
Adds h003 schematic support
Browse files Browse the repository at this point in the history
  • Loading branch information
jplot committed Sep 4, 2024
1 parent 0ff3557 commit 23a38ad
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 80 deletions.
96 changes: 69 additions & 27 deletions lib/epics/client.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
class Epics::Client
extend Forwardable

attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :keys, :keys_content
attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :keys, :keys_content, :current_order_id
attr_reader :version
attr_writer :iban, :bic, :name
attr_accessor :locale

def_delegators :connection, :post

VERSION_H3 = 'H003'
VERSION_H4 = 'H004'

VERSIONS = [VERSION_H3, VERSION_H4]

USER_AGENT = "EPICS v#{Epics::VERSION}"

def initialize(keys_content, passphrase, url, host_id, user_id, partner_id)
self.keys_content = keys_content.respond_to?(:read) ? keys_content.read : keys_content if keys_content
self.passphrase = passphrase
Expand All @@ -16,6 +24,25 @@ def initialize(keys_content, passphrase, url, host_id, user_id, partner_id)
self.user_id = user_id
self.partner_id = partner_id
self.locale = :de
self.current_order_id = 0
self.version = VERSION_H4

yield self if block_given?
end

def version=(version)
raise ArgumentError, "Unsupported version: #{version}" unless VERSIONS.include?(version)

@version = version
end

def urn_schema
case version
when VERSION_H3
"https://www.ebics.org/#{version}"
when VERSION_H4
"urn:org:ebics:#{version}"
end
end

def inspect
Expand All @@ -25,24 +52,41 @@ def inspect
@partner_id=\"#{self.partner_id}\""
end

def e
keys["E002"]
def next_order_id
raise 'Order ID overflow' if current_order_id >= 1679615
self.current_order_id += 1
end

def a
keys["A006"]
def encryption_version
'E002'
end

def x
keys["X002"]
def encryption_key
keys[encryption_version]
end

def bank_e
keys["#{host_id.upcase}.E002"]
def signature_version
'A006'
end

def bank_x
keys["#{host_id.upcase}.X002"]
def signature_key
keys[signature_version]
end

def authentication_version
'X002'
end

def authentication_key
keys[authentication_version]
end

def bank_encryption_key
keys["#{host_id.upcase}.#{encryption_version}"]
end

def bank_authentication_key
keys["#{host_id.upcase}.#{authentication_version}"]
end

def name
Expand All @@ -61,9 +105,9 @@ def order_types
@order_types ||= (self.HTD; @order_types)
end

def self.setup(passphrase, url, host_id, user_id, partner_id, keysize = 2048)
client = new(nil, passphrase, url, host_id, user_id, partner_id)
client.keys = %w(A006 X002 E002).each_with_object({}) do |type, memo|
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) )
end

Expand Down Expand Up @@ -108,7 +152,7 @@ def INI
end

def HPB
Nokogiri::XML(download(Epics::HPB)).xpath("//xmlns:PubKeyValue", xmlns: "urn:org:ebics:H004").each do |node|
Nokogiri::XML(download(Epics::HPB)).xpath("//xmlns:PubKeyValue", xmlns: urn_schema).each do |node|
type = node.parent.last_element_child.content

modulus = Base64.decode64(node.at_xpath(".//*[local-name() = 'Modulus']").content)
Expand All @@ -123,7 +167,7 @@ def HPB
self.keys["#{host_id.upcase}.#{type}"] = Epics::Key.new(bank)
end

[bank_x, bank_e]
[bank_authentication_key, bank_encryption_key]
end

def AZV(document)
Expand Down Expand Up @@ -215,15 +259,15 @@ def Z54(from, to)
end

def HAA
Nokogiri::XML(download(Epics::HAA)).at_xpath("//xmlns:OrderTypes", xmlns: "urn:org:ebics:H004").content.split(/\s/)
Nokogiri::XML(download(Epics::HAA)).at_xpath("//xmlns:OrderTypes", xmlns: urn_schema).content.split(/\s/)
end

def HTD
Nokogiri::XML(download(Epics::HTD)).tap do |htd|
@iban ||= htd.at_xpath("//xmlns:AccountNumber[@international='true']", xmlns: "urn:org:ebics:H004").text rescue nil
@bic ||= htd.at_xpath("//xmlns:BankCode[@international='true']", xmlns: "urn:org:ebics:H004").text rescue nil
@name ||= htd.at_xpath("//xmlns:Name", xmlns: "urn:org:ebics:H004").text rescue nil
@order_types ||= htd.search("//xmlns:OrderTypes", xmlns: "urn:org:ebics:H004").map{|o| o.content.split(/\s/) }.delete_if{|o| o == ""}.flatten
@iban ||= htd.at_xpath("//xmlns:AccountNumber[@international='true']", xmlns: urn_schema).text rescue nil
@bic ||= htd.at_xpath("//xmlns:BankCode[@international='true']", xmlns: urn_schema).text rescue nil
@name ||= htd.at_xpath("//xmlns:Name", xmlns: urn_schema).text rescue nil
@order_types ||= htd.search("//xmlns:OrderTypes", xmlns: urn_schema).map{|o| o.content.split(/\s/) }.delete_if{|o| o == ""}.flatten
end.to_xml
end

Expand Down Expand Up @@ -251,14 +295,12 @@ def save_keys(path)

def upload(order_type, document)
order = order_type.new(self, document)
res = post(url, order.to_xml).body
order.transaction_id = res.transaction_id

order_id = res.order_id
session = post(url, order.to_xml).body
order.transaction_id = session.transaction_id

res = post(url, order.to_transfer_xml).body

return res.transaction_id, [res.order_id, order_id].detect { |id| id.to_s.chars.any? }
return res.transaction_id, [res.order_id, session.order_id].detect { |id| id.to_s.chars.any? }
end

def download(order_type, *args, **options)
Expand All @@ -282,7 +324,7 @@ def download_and_unzip(order_type, *args, **options)
end

def connection
@connection ||= Faraday.new(headers: { 'Content-Type' => 'text/xml', user_agent: "EPICS v#{Epics::VERSION}"}, 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 Down
6 changes: 3 additions & 3 deletions lib/epics/generic_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def auth_signature

def to_transfer_xml
Nokogiri::XML::Builder.new do |xml|
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'urn:org:ebics:H004', 'Version' => 'H004', 'Revision' => '1') {
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => client.urn_schema, 'Version' => client.version, 'Revision' => '1') {
xml.header(authenticate: true) {
xml.static {
xml.HostID host_id
Expand All @@ -76,7 +76,7 @@ def to_transfer_xml

def to_receipt_xml
Nokogiri::XML::Builder.new do |xml|
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'urn:org:ebics:H004', 'Version' => 'H004', 'Revision' => '1') {
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => client.urn_schema, 'Version' => client.version, 'Revision' => '1') {
xml.header(authenticate: true) {
xml.static {
xml.HostID host_id
Expand All @@ -98,7 +98,7 @@ def to_receipt_xml

def to_xml
Nokogiri::XML::Builder.new do |xml|
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'urn:org:ebics:H004', 'Version' => 'H004', 'Revision'=> '1') {
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => client.urn_schema, 'Version' => client.version, 'Revision'=> '1') {
xml.parent.add_child(header)
xml.parent.add_child(auth_signature)
xml.parent.add_child(body)
Expand Down
8 changes: 4 additions & 4 deletions lib/epics/generic_upload_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def body
xml.body {
xml.DataTransfer {
xml.DataEncryptionInfo(authenticate: true) {
xml.EncryptionPubKeyDigest(client.bank_e.public_digest, Version: 'E002', Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256")
xml.TransactionKey Base64.encode64(client.bank_e.key.public_encrypt(self.key)).gsub(/\n/,'')
xml.EncryptionPubKeyDigest(client.bank_encryption_key.public_digest, Version: client.encryption_version, Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256")
xml.TransactionKey Base64.encode64(client.bank_encryption_key.key.public_encrypt(self.key)).gsub(/\n/,'')
}
xml.SignatureData(encrypted_order_signature, authenticate: true)
}
Expand All @@ -36,7 +36,7 @@ def order_signature
Nokogiri::XML::Builder.new do |xml|
xml.UserSignatureData('xmlns' => 'http://www.ebics.org/S001', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation' => 'http://www.ebics.org/S001 http://www.ebics.org/S001/ebics_signature.xsd') {
xml.OrderSignatureData {
xml.SignatureVersion "A006"
xml.SignatureVersion client.signature_version
xml.SignatureValue signature_value
xml.PartnerID partner_id
xml.UserID user_id
Expand All @@ -46,7 +46,7 @@ def order_signature
end

def signature_value
client.a.sign( digester.digest(document.gsub(/\n|\r/, "")) )
client.signature_key.sign( digester.digest(document.gsub(/\n|\r/, "")) )
end

def encrypt(d)
Expand Down
16 changes: 14 additions & 2 deletions lib/epics/header_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ class Epics::HeaderRequest
extend Forwardable
attr_accessor :client

BASE36_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'

PRODUCT_NAME = 'EPICS - a ruby ebics kernel'
PRODUCT_LANG = 'de'

Expand All @@ -25,14 +27,15 @@ def build(options = {})
xml.Product(PRODUCT_NAME, 'Language' => PRODUCT_LANG)
xml.OrderDetails {
xml.OrderType options[:order_type]
xml.OrderId b36encode(client.next_order_id).rjust(4, '0') if client.version == Epics::Client::VERSION_H3
xml.OrderAttribute options[:order_attribute]
xml.StandardOrderParams {
build_attributes(xml, options[:order_params])
} if options[:order_params]
}
xml.BankPubKeyDigests {
xml.Authentication(client.bank_x.public_digest, Version: 'X002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256')
xml.Encryption(client.bank_e.public_digest, Version: 'E002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256')
xml.Authentication(client.bank_authentication_key.public_digest, Version: client.authentication_version, Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256')
xml.Encryption(client.bank_encryption_key.public_digest, Version: client.encryption_version, Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256')
} if options[:with_bank_pubkey_digests]
xml.SecurityMedium '0000'
xml.NumSegments options[:num_segments] if options[:num_segments]
Expand All @@ -57,4 +60,13 @@ def build_attributes(xml, attributes)
end
end
end

def b36encode(number)
str = ''
while number > 0
number, i = number.divmod(36)
str += BASE36_ALPHABET[i]
end
str.empty? ? '0' : str
end
end
16 changes: 8 additions & 8 deletions lib/epics/hia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,24 @@ def body

def order_data
Nokogiri::XML::Builder.new do |xml|
xml.HIARequestOrderData('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'urn:org:ebics:H004') {
xml.HIARequestOrderData('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => client.urn_schema) {
xml.AuthenticationPubKeyInfo {
xml.PubKeyValue {
xml.send('ds:RSAKeyValue') {
xml.send('ds:Modulus', Base64.strict_encode64([client.x.n].pack("H*")))
xml.send('ds:Exponent', Base64.strict_encode64(client.x.key.e.to_s(2)))
xml.send('ds:Modulus', Base64.strict_encode64([client.authentication_key.n].pack("H*")))
xml.send('ds:Exponent', Base64.strict_encode64(client.authentication_key.key.e.to_s(2)))
}
}
xml.AuthenticationVersion 'X002'
xml.AuthenticationVersion client.authentication_version
}
xml.EncryptionPubKeyInfo{
xml.PubKeyValue {
xml.send('ds:RSAKeyValue') {
xml.send('ds:Modulus', Base64.strict_encode64([client.e.n].pack("H*")))
xml.send('ds:Exponent', Base64.strict_encode64(client.e.key.e.to_s(2)))
xml.send('ds:Modulus', Base64.strict_encode64([client.encryption_key.n].pack("H*")))
xml.send('ds:Exponent', Base64.strict_encode64(client.encryption_key.key.e.to_s(2)))
}
}
xml.EncryptionVersion 'E002'
xml.EncryptionVersion client.encryption_version
}
xml.PartnerID partner_id
xml.UserID user_id
Expand All @@ -51,7 +51,7 @@ def order_data

def to_xml
Nokogiri::XML::Builder.new do |xml|
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'urn:org:ebics:H004', 'Version' => 'H004', 'Revision' => '1') {
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => client.urn_schema, 'Version' => client.version, 'Revision' => '1') {
xml.parent.add_child(header)
xml.parent.add_child(body)
}
Expand Down
8 changes: 4 additions & 4 deletions lib/epics/ini.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ def key_signature
xml.SignaturePubKeyInfo {
xml.PubKeyValue {
xml.send('ds:RSAKeyValue') {
xml.send('ds:Modulus', Base64.strict_encode64([client.a.n].pack("H*")))
xml.send('ds:Exponent', Base64.strict_encode64(client.a.key.e.to_s(2)))
xml.send('ds:Modulus', Base64.strict_encode64([client.signature_key.n].pack("H*")))
xml.send('ds:Exponent', Base64.strict_encode64(client.signature_key.key.e.to_s(2)))
}
xml.TimeStamp timestamp
}
xml.SignatureVersion 'A006'
xml.SignatureVersion client.signature_version
}
xml.PartnerID partner_id
xml.UserID user_id
Expand All @@ -43,7 +43,7 @@ def key_signature

def to_xml
Nokogiri::XML::Builder.new do |xml|
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'urn:org:ebics:H004', 'Version' => 'H004', 'Revision' => '1') {
xml.send(root, 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => client.urn_schema, 'Version' => client.version, 'Revision' => '1') {
xml.parent.add_child(header)
xml.parent.add_child(body)
}
Expand Down
4 changes: 2 additions & 2 deletions lib/epics/key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def sign(msg)
msg,
salt_length: :digest,
mgf1_hash: 'SHA256',
),
).gsub("\n", '')
),
).gsub("\n", '')
end

def digester
Expand Down
Loading

0 comments on commit 23a38ad

Please sign in to comment.