From 99ebf63cd58721683b813a0b4efdaf76f4d330cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Fri, 3 Jul 2026 13:13:07 +0200 Subject: [PATCH 01/12] Add EBICS 3.0 (H005) support alongside 2.5 (H004) Introduce an opt-in `version:` client option (default :h004) so existing 2.5 users are unaffected. The crypto layer (A006/X002/E002) already matches H005, so the changes are the XML envelope, OrderDetails/BTF structure, response parsing and X.509 key management. - Version descriptor table + client namespace/version readers - Parameterized request envelope and response/client XPaths by namespace - Version-branched OrderDetails: H005 AdminOrderType + BTF Service, empty SignatureFlag (with requestEDS), StandardOrderParams for admin downloads - New BTU/BTD orders + Epics::BTF value object + BtfMapping (German starter set); common convenience methods route to BTU/BTD under H005 - Mandatory H005 upload DataDigest (A006) - H005 key management: self-signed X.509 cert generation, INI/HIA/HPB send certs only (S002 namespace, no RSAKeyValue) - Bundled official H005 XSD set; matcher is version-aware and the suite XSD-validates every H005 request plus the INI S002 payload - 225 examples, 0 failures (all original H004 specs unchanged) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 38 + lib/epics.rb | 13 + lib/epics/btd.rb | 20 + lib/epics/btf.rb | 57 + lib/epics/btf_mapping.rb | 50 + lib/epics/btu.rb | 20 + lib/epics/client.rb | 98 +- lib/epics/generic_request.rb | 17 +- lib/epics/generic_upload_request.rb | 16 +- lib/epics/header_request.rb | 112 +- lib/epics/hia.rb | 27 +- lib/epics/hpb.rb | 2 + lib/epics/ini.rb | 22 +- lib/epics/response.rb | 28 +- lib/epics/x_509_certificate.rb | 36 +- spec/btf_spec.rb | 36 + spec/h005_client_spec.rb | 112 ++ spec/orders/btd_spec.rb | 42 + spec/orders/btu_spec.rb | 59 + spec/support/ebics_matcher.rb | 28 +- spec/xsd/ebics_H005.xsd | 11 + spec/xsd/ebics_keymgmt_request_H005.xsd | 523 ++++++ spec/xsd/ebics_keymgmt_response_H005.xsd | 137 ++ spec/xsd/ebics_orders_H005.xsd | 2094 ++++++++++++++++++++++ spec/xsd/ebics_request_H005.xsd | 349 ++++ spec/xsd/ebics_response_H005.xsd | 167 ++ spec/xsd/ebics_signature_S002.xsd | 177 ++ spec/xsd/ebics_types_H005.xsd | 1885 +++++++++++++++++++ 28 files changed, 6133 insertions(+), 43 deletions(-) create mode 100644 lib/epics/btd.rb create mode 100644 lib/epics/btf.rb create mode 100644 lib/epics/btf_mapping.rb create mode 100644 lib/epics/btu.rb create mode 100644 spec/btf_spec.rb create mode 100644 spec/h005_client_spec.rb create mode 100644 spec/orders/btd_spec.rb create mode 100644 spec/orders/btu_spec.rb create mode 100644 spec/xsd/ebics_H005.xsd create mode 100644 spec/xsd/ebics_keymgmt_request_H005.xsd create mode 100644 spec/xsd/ebics_keymgmt_response_H005.xsd create mode 100644 spec/xsd/ebics_orders_H005.xsd create mode 100644 spec/xsd/ebics_request_H005.xsd create mode 100644 spec/xsd/ebics_response_H005.xsd create mode 100644 spec/xsd/ebics_signature_S002.xsd create mode 100644 spec/xsd/ebics_types_H005.xsd diff --git a/README.md b/README.md index 77d6f4a6..947c2ca1 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,44 @@ You can choose to configure some default values like this e = Epics::Client.new(keys, 'passphrase', 'url', 'host', 'user', 'partner', locale: :fr, product_name: 'Mon Epic Client EBICS') ``` +### EBICS 3.0 (H005) + +The gem defaults to EBICS 2.5 (`H004`). To use EBICS 3.0 (`H005`), pass `version: :h005`: + +```ruby +e = Epics::Client.new(keys, 'passphrase', 'url', 'host', 'user', 'partner', version: :h005) +``` + +In EBICS 3.0 the classic order types are replaced by the Business Transaction +Format (BTF): uploads use `BTU`, downloads use `BTD`, each described by a +`Service` instead of a `FileFormat`. + +```ruby +# Upload (replaces FUL). `service` is an Epics::BTF (or a hash with the same keys). +e.BTU(document, Epics::BTF.new(service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_version: '03')) + +# Download (replaces FDL). +e.BTD(Epics::BTF.new(service_name: 'EOP', scope: 'DE', container: 'ZIP', msg_name: 'camt.053', msg_version: '08'), from: '2026-01-01', to: '2026-01-31') +``` + +The common German convenience methods (`CCT`, `CDD`, `CDB`, `STA`, `C52`, `C53`, +`C54`, `VMK`) automatically route to `BTU`/`BTD` via `Epics::BtfMapping` when the +client is on `:h005`. The BTF message versions in that table are commonly-used +defaults and **are bank-specific** — verify them against your bank's BTF mapping +("Auftragsarten" annex) and override with the raw `BTU`/`BTD` API when needed. + +EBICS 3.0 requires every key to be transmitted as an X.509 certificate. For +shared-key banks (e.g. German banks) the gem generates self-signed certificates +automatically for `INI`/`HIA` when no certificate is supplied. + +> Note: All generated H005 requests (INI/HIA/HPB, the admin downloads, and +> BTU/BTD in every transaction phase) are validated against the official H005 +> XSD schema set in the test suite, and the INI key payload against the S002 +> signature schema. Full end-to-end verification against a live H005 bank +> endpoint is the remaining step. Known limitation: the `X509IssuerSerial` +> currently reports the certificate version rather than its serial number +> (pre-existing behaviour shared with the H004 X.509 path). + ## Features ### Initialization diff --git a/lib/epics.rb b/lib/epics.rb index bf681fbd..85ed1850 100644 --- a/lib/epics.rb +++ b/lib/epics.rb @@ -59,6 +59,10 @@ require "epics/hia" require "epics/ini" require "epics/hev" +require "epics/btf" +require "epics/btf_mapping" +require "epics/btd" +require "epics/btu" require "epics/signer" require "epics/x_509_certificate" require "epics/client" @@ -68,6 +72,15 @@ module Epics DEFAULT_PRODUCT_NAME = 'EPICS - a ruby ebics kernel' DEFAULT_LOCALE = :de + DEFAULT_VERSION = :h004 + + # EBICS protocol version descriptors. The gem defaults to H004 (EBICS 2.5) so + # that existing users are unaffected; H005 (EBICS 3.0) is opt-in via the + # `version:` client option. + EBICS_PROTOCOLS = { + h004: { namespace: 'urn:org:ebics:H004', version: 'H004', revision: '1' }, + h005: { namespace: 'urn:org:ebics:H005', version: 'H005', revision: '1' }, + }.freeze end Ebics = Epics diff --git a/lib/epics/btd.rb b/lib/epics/btd.rb new file mode 100644 index 00000000..bdc2b54d --- /dev/null +++ b/lib/epics/btd.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# EBICS 3.0 (H005) generic download order. Replaces the H004 FDL order: instead +# of a FileFormat string the transfer is described by a BTF . +# +# client.BTD(Epics::BTF.new(service_name: "EOP", scope: "DE", msg_name: "camt.053", msg_version: "08"), from: "2026-01-01", to: "2026-01-31") +class Epics::BTD < Epics::GenericRequest + def header + client.header_request.build( + nonce: nonce, + timestamp: timestamp, + admin_order_type: 'BTD', + service: options[:service], + from: options[:from], + to: options[:to], + parameters: options[:parameters], + mutable: { TransactionPhase: 'Initialisation' } + ) + end +end diff --git a/lib/epics/btf.rb b/lib/epics/btf.rb new file mode 100644 index 00000000..f80d3303 --- /dev/null +++ b/lib/epics/btf.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Value object describing an EBICS 3.0 (H005) Business Transaction Format (BTF). +# +# A BTF replaces the H004 OrderType/FileFormat combination. It is expressed via +# the element inside BTU/BTD order params: +# +# +# SCT +# ... (optional) +# DE (optional) +# (optional) +# pain.001 +# +# +# Example: +# +# Epics::BTF.new( +# service_name: "SCT", +# scope: "DE", +# msg_name: "pain.001", +# msg_version: "03", +# ) +# +# A plain Hash with the same keys is accepted anywhere a BTF is expected. +class Epics::BTF + attr_reader :service_name, :service_option, :scope, :container, + :msg_name, :msg_version, :msg_variant, :msg_format + + def initialize(service_name:, msg_name:, scope: nil, service_option: nil, + container: nil, msg_version: nil, msg_variant: nil, msg_format: nil) + @service_name = service_name + @service_option = service_option + @scope = scope + @container = container + @msg_name = msg_name + @msg_version = msg_version + @msg_variant = msg_variant + @msg_format = msg_format + end + + # Normalized hash consumed by Epics::HeaderRequest#build_service. + def to_h + { + service_name: service_name, + service_option: service_option, + scope: scope, + container: container, + msg_name: { + name: msg_name, + version: msg_version, + variant: msg_variant, + format: msg_format, + }, + } + end +end diff --git a/lib/epics/btf_mapping.rb b/lib/epics/btf_mapping.rb new file mode 100644 index 00000000..03779da6 --- /dev/null +++ b/lib/epics/btf_mapping.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Maps the classic H004 order codes (CCT, CDD, C53, ...) to their EBICS 3.0 +# (H005) BTF Service descriptors, so the existing convenience API keeps working +# when a client is configured with `version: :h005`. +# +# This is a *starter set* for German (DE) banks. The message versions below are +# the commonly used ISO 20022 versions but ARE bank-specific — verify them +# against your bank's BTF mapping / "Auftragsarten" annex and override via the +# raw Epics::Client#BTU / #BTD API (passing an Epics::BTF) when they differ. +# +# Unmapped codes raise, pointing the caller at the raw BTU/BTD API. +module Epics::BtfMapping + # code => [direction, btf-attributes] + UPLOADS = { + 'CCT' => { service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_version: '03' }, + 'CCS' => { service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_version: '03' }, + 'CDD' => { service_name: 'SDD', service_option: 'COR', scope: 'DE', msg_name: 'pain.008', msg_version: '02' }, + 'CDB' => { service_name: 'SDD', service_option: 'B2B', scope: 'DE', msg_name: 'pain.008', msg_version: '02' }, + }.freeze + + DOWNLOADS = { + 'STA' => { service_name: 'EOP', scope: 'DE', msg_name: 'mt940' }, + 'C53' => { service_name: 'EOP', scope: 'DE', container: 'ZIP', msg_name: 'camt.053', msg_version: '08' }, + 'C52' => { service_name: 'STM', scope: 'DE', container: 'ZIP', msg_name: 'camt.052', msg_version: '08' }, + 'C54' => { service_name: 'REP', scope: 'DE', container: 'ZIP', msg_name: 'camt.054', msg_version: '08' }, + 'VMK' => { service_name: 'STM', scope: 'DE', msg_name: 'mt942' }, + 'PSR' => { service_name: 'PSR', scope: 'DE', msg_name: 'pain.002', msg_version: '03' }, + }.freeze + + module_function + + def upload(code) + lookup(UPLOADS, code) + end + + def download(code) + lookup(DOWNLOADS, code) + end + + def lookup(table, code) + attrs = table[code.to_s] + unless attrs + raise ArgumentError, + "No H005 BTF mapping for order code #{code.inspect}. Use the raw " \ + "Epics::Client#BTU / #BTD API with an Epics::BTF instead." + end + Epics::BTF.new(**attrs) + end +end diff --git a/lib/epics/btu.rb b/lib/epics/btu.rb new file mode 100644 index 00000000..ebcb2903 --- /dev/null +++ b/lib/epics/btu.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# EBICS 3.0 (H005) generic upload order. Replaces the H004 FUL order: instead of +# a FileFormat string the transfer is described by a BTF . +# +# client.BTU(document, Epics::BTF.new(service_name: "SCT", scope: "DE", msg_name: "pain.001", msg_version: "03")) +class Epics::BTU < Epics::GenericUploadRequest + def header + client.header_request.build( + nonce: nonce, + timestamp: timestamp, + admin_order_type: 'BTU', + service: options[:service], + signature_flag: options.fetch(:signature_flag, true), + request_eds: options[:request_eds], + parameters: options[:parameters], + mutable: { TransactionPhase: 'Initialisation' } + ) + end +end diff --git a/lib/epics/client.rb b/lib/epics/client.rb index c71bc300..594263a3 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -2,7 +2,7 @@ class Epics::Client extend Forwardable attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :keys, :keys_content, :locale, :product_name, - :x_509_certificates_content, :debug_mode + :x_509_certificates_content, :debug_mode, :ebics_version attr_writer :iban, :bic, :name @@ -19,6 +19,10 @@ def initialize(keys_content, passphrase, url, host_id, user_id, partner_id, opti self.locale = options[:locale] || Epics::DEFAULT_LOCALE self.product_name = options[:product_name] || Epics::DEFAULT_PRODUCT_NAME self.debug_mode = !!options[:debug_mode] + self.ebics_version = (options[:version] || Epics::DEFAULT_VERSION).to_s.downcase.to_sym + unless Epics::EBICS_PROTOCOLS.key?(ebics_version) + raise ArgumentError, "Unsupported EBICS version #{ebics_version.inspect}, expected one of #{Epics::EBICS_PROTOCOLS.keys.inspect}" + end self.x_509_certificates_content = { a: options[:x_509_certificate_a_content], x: options[:x_509_certificate_x_content], @@ -26,6 +30,26 @@ def initialize(keys_content, passphrase, url, host_id, user_id, partner_id, opti } end + def protocol + Epics::EBICS_PROTOCOLS.fetch(ebics_version) + end + + def namespace + protocol[:namespace] + end + + def protocol_version + protocol[:version] + end + + def revision + protocol[:revision] + end + + def h005? + ebics_version == :h005 + end + def inspect "#<#{self.class}:#{self.object_id} @keys=#{self.keys.keys}, @@ -123,7 +147,7 @@ def HEV 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: namespace).each do |node| type = node.parent.last_element_child.content modulus = Base64.decode64(node.at_xpath(".//*[local-name() = 'Modulus']").content) @@ -150,6 +174,7 @@ def CD1(document) end def CDB(document) + return btf_upload('CDB', document) if h005? upload(Epics::CDB, document) end @@ -158,6 +183,7 @@ def C2S(document) end def CDD(document) + return btf_upload('CDD', document) if h005? upload(Epics::CDD, document) end @@ -178,6 +204,7 @@ def XDS(document) end def CCT(document) + return btf_upload('CCT', document) if h005? upload(Epics::CCT, document) end @@ -186,6 +213,7 @@ def CIP(document) end def CCS(document) + return btf_upload('CCS', document) if h005? upload(Epics::CCS, document) end @@ -197,7 +225,20 @@ def FUL(document) upload(Epics::FUL, document) end + # EBICS 3.0 (H005) generic upload. `service` is an Epics::BTF (or a hash with + # the same keys). Replaces FUL under H005. + def BTU(document, service, signature_flag: true, request_eds: false, parameters: nil) + upload(Epics::BTU, document, service: service, signature_flag: signature_flag, request_eds: request_eds, parameters: parameters) + end + + # EBICS 3.0 (H005) generic download. `service` is an Epics::BTF (or a hash with + # the same keys). Replaces FDL under H005. + def BTD(service, from: nil, to: nil, parameters: nil) + download(Epics::BTD, service: service, from: from, to: to, parameters: parameters) + end + def STA(from = nil, to = nil) + return btf_download('STA', from, to) if h005? download(Epics::STA, from: from, to: to) end @@ -206,6 +247,7 @@ def FDL(format, from = nil, to = nil) end def VMK(from = nil, to = nil) + return btf_download('VMK', from, to) if h005? download(Epics::VMK, from: from, to: to) end @@ -222,14 +264,17 @@ def BKA(from, to) end def C52(from, to) + return btf_download('C52', from, to) if h005? download_and_unzip(Epics::C52, from: from, to: to) end def C53(from, to) + return btf_download('C53', from, to) if h005? download_and_unzip(Epics::C53, from: from, to: to) end def C54(from, to) + return btf_download('C54', from, to) if h005? download_and_unzip(Epics::C54, from: from, to: to) end @@ -254,15 +299,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: namespace).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: namespace).text rescue nil + @bic ||= htd.at_xpath("//xmlns:BankCode[@international='true']", xmlns: namespace).text rescue nil + @name ||= htd.at_xpath("//xmlns:Name", xmlns: namespace).text rescue nil + @order_types ||= htd.search("//xmlns:OrderTypes", xmlns: namespace).map{|o| o.content.split(/\s/) }.delete_if{|o| o == ""}.flatten end.to_xml end @@ -292,7 +337,13 @@ def save_keys(path) def x_509_certificate(type) content = x_509_certificates_content[type.to_sym] - return if content.nil? || content.empty? + if content.nil? || content.empty? + # EBICS 3.0 mandates X.509 certificates for all keys. When none was + # supplied, fall back to a self-signed certificate (valid for shared-key + # banks, e.g. German banks). H004 keeps the raw RSAKeyValue behaviour. + return unless h005? + return self_signed_certificate(type) + end Epics::X509Certificate.new(content) end @@ -305,8 +356,35 @@ def x_509_certificate_hash(type) private - def upload(order_type, document) - order = order_type.new(self, document) + KEY_FOR_CERT_TYPE = { a: 'A006', x: 'X002', e: 'E002' }.freeze + + def self_signed_certificate(type) + key = keys[KEY_FOR_CERT_TYPE.fetch(type.to_sym)] + return unless key + @self_signed_certificates ||= {} + @self_signed_certificates[type.to_sym] ||= + Epics::X509Certificate.generate_self_signed( + key.key, + subject: "/CN=#{user_id}/O=#{partner_id}/OU=#{host_id}" + ) + end + + # Route a classic order code to its H005 BTF Service via Epics::BtfMapping. + def btf_upload(code, document) + self.BTU(document, Epics::BtfMapping.upload(code)) + end + + def btf_download(code, from, to) + btf = Epics::BtfMapping.download(code) + if btf.container == 'ZIP' + download_and_unzip(Epics::BTD, service: btf, from: from, to: to) + else + self.BTD(btf, from: from, to: to) + end + end + + def upload(order_type, document, **options) + order = order_type.new(self, document, **options) res = post(url, order.to_xml).body order.transaction_id = res.transaction_id diff --git a/lib/epics/generic_request.rb b/lib/epics/generic_request.rb index 62902c5b..627ce5ad 100644 --- a/lib/epics/generic_request.rb +++ b/lib/epics/generic_request.rb @@ -22,6 +22,17 @@ def root "ebicsRequest" end + # Namespace/version attributes for the request root element. Derived from the + # client's configured EBICS protocol version (H004 by default, H005 opt-in). + def root_attributes + { + 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', + 'xmlns' => client.namespace, + 'Version' => client.protocol_version, + 'Revision' => client.revision, + } + end + def body Nokogiri::XML::Builder.new do |xml| xml.body @@ -53,7 +64,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, root_attributes) { xml.header(authenticate: true) { xml.static { xml.HostID host_id @@ -76,7 +87,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, root_attributes) { xml.header(authenticate: true) { xml.static { xml.HostID host_id @@ -98,7 +109,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, root_attributes) { xml.parent.add_child(header) xml.parent.add_child(auth_signature) xml.parent.add_child(body) diff --git a/lib/epics/generic_upload_request.rb b/lib/epics/generic_upload_request.rb index 8d71fdc6..508ecba9 100644 --- a/lib/epics/generic_upload_request.rb +++ b/lib/epics/generic_upload_request.rb @@ -27,14 +27,28 @@ def body xml.TransactionKey Base64.encode64(client.bank_e.key.public_encrypt(self.key)).gsub(/\n/,'') } xml.SignatureData(encrypted_order_signature, authenticate: true) + # EBICS 3.0 (H005) additionally carries the plain hash of the order + # data (the value that was signed) as a DataDigest element. + xml.DataDigest(data_digest, SignatureVersion: 'A006') if client.h005? } } end.doc.root end + # Base64-encoded SHA-256 digest of the order data — the same digest that is + # signed in #signature_value. Required in the H005 upload body. + def data_digest + Base64.strict_encode64(digester.digest(document.gsub(/\n|\r/, ""))) + end + + def signature_namespace + client.h005? ? 'http://www.ebics.org/S002' : 'http://www.ebics.org/S001' + end + def order_signature + ns = signature_namespace 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.UserSignatureData('xmlns' => ns, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation' => "#{ns} #{ns}/ebics_signature.xsd") { xml.OrderSignatureData { xml.SignatureVersion "A006" xml.SignatureValue signature_value diff --git a/lib/epics/header_request.rb b/lib/epics/header_request.rb index fc4ee043..766fbe57 100644 --- a/lib/epics/header_request.rb +++ b/lib/epics/header_request.rb @@ -21,12 +21,7 @@ def build(options = {}) xml.UserID user_id xml.Product(client.product_name, 'Language' => client.locale) xml.OrderDetails { - xml.OrderType options[:order_type] - xml.OrderAttribute options[:order_attribute] - xml.StandardOrderParams { - build_attributes(xml, options[:order_params]) - } if options[:order_params] - build_attributes(xml, options[:custom_order_params]) if options[:custom_order_params] + build_order_details(xml, options) } xml.BankPubKeyDigests { xml.Authentication(client.bank_x.public_digest, Version: 'X002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256') @@ -44,6 +39,111 @@ def build(options = {}) private + # EBICS 3.0 (H005) uses AdminOrderType plus a BTF Service structure, and drops + # OrderAttribute. EBICS 2.5 (H004) keeps the classic OrderType/OrderAttribute + # shape. The version branch is the only structural divergence in the header. + def build_order_details(xml, options) + if client.h005? + build_h005_order_details(xml, options) + else + build_h004_order_details(xml, options) + end + end + + def build_h004_order_details(xml, options) + xml.OrderType options[:order_type] + xml.OrderAttribute options[:order_attribute] + xml.StandardOrderParams { + build_attributes(xml, options[:order_params]) + } if options[:order_params] + build_attributes(xml, options[:custom_order_params]) if options[:custom_order_params] + end + + def build_h005_order_details(xml, options) + admin_order_type = options[:admin_order_type] || options[:order_type] + xml.AdminOrderType admin_order_type + + case admin_order_type + when 'BTU' + xml.BTUOrderParams { + build_service(xml, options[:service]) + # SignatureFlag is an empty element (H005). Its *presence* means the + # order carries an electronic signature and is authorised within EBICS; + # omitting it means the order is authorised outside EBICS. The optional + # requestEDS attribute spools the order into the distributed signature + # (EDS/VEU) queue. + if options.fetch(:signature_flag, true) + attrs = options[:request_eds] ? { 'requestEDS' => 'true' } : {} + xml.SignatureFlag(attrs) + end + build_parameters(xml, options[:parameters]) + } + when 'BTD' + xml.BTDOrderParams { + build_service(xml, options[:service]) + build_date_range(xml, options) + build_parameters(xml, options[:parameters]) + } + else + # Other admin order types retrieved via ebicsRequest (HTD, HAA, HKD, HPD, + # HAC, ...) require a StandardOrderParams element. INI/HIA/HPB use the + # unsecured / no-pub-key request schemas whose OrderDetails carry only the + # AdminOrderType, so they suppress it via with_order_params: false. + if options.fetch(:with_order_params, true) + xml.StandardOrderParams { + build_date_range(xml, options) + } + end + end + end + + # Builds the BTF element. The child element order follows the H005 + # schema sequence: ServiceName, Scope, ServiceOption, Container, MsgName. + def build_service(xml, service) + return if service.nil? + service = service.to_h if service.respond_to?(:to_h) + + xml.Service { + xml.ServiceName service[:service_name] || service[:ServiceName] + scope = service[:scope] || service[:Scope] + xml.Scope scope if scope + option = service[:service_option] || service[:ServiceOption] + xml.ServiceOption option if option + + container = service[:container] || service[:Container] + if container + xml.Container('containerType' => container) + end + + msg = service[:msg_name] || service[:MsgName] || {} + msg = { name: msg } unless msg.is_a?(Hash) + msg_attrs = {} + msg_attrs['version'] = msg[:version] if msg[:version] + msg_attrs['variant'] = msg[:variant] if msg[:variant] + msg_attrs['format'] = msg[:format] if msg[:format] + xml.MsgName(msg[:name] || msg[:value], msg_attrs) + } + end + + def build_date_range(xml, options) + if options[:from] && options[:to] + xml.DateRange { + xml.Start options[:from] + xml.End options[:to] + } + end + end + + def build_parameters(xml, parameters) + return if parameters.nil? + parameters.each do |name, value| + xml.Parameter { + xml.Name name + xml.Value(value, 'Type' => 'string') + } + end + end + def build_attributes(xml, attributes) attributes.each do |key, value| if value.is_a?(Hash) diff --git a/lib/epics/hia.rb b/lib/epics/hia.rb index f43879dc..8ea6b27f 100644 --- a/lib/epics/hia.rb +++ b/lib/epics/hia.rb @@ -6,8 +6,10 @@ def root def header client.header_request.build( order_type: 'HIA', + admin_order_type: 'HIA', order_attribute: 'DZNNN', with_bank_pubkey_digests: false, + with_order_params: false, mutable: {} ) end @@ -23,8 +25,10 @@ def body end def order_data + return h005_order_data if client.h005? + 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.namespace) { xml.AuthenticationPubKeyInfo { x509_data_xml(xml, client.x_509_certificate(:x)) xml.PubKeyValue { @@ -51,9 +55,28 @@ def order_data end.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML, encoding: 'utf-8') end + # EBICS 3.0 (H005): public keys are carried exclusively as X.509 certificates + # (ds:X509Data); the H004 RSAKeyValue is no longer part of the structure. + def h005_order_data + Nokogiri::XML::Builder.new do |xml| + xml.HIARequestOrderData('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => client.namespace) { + xml.AuthenticationPubKeyInfo { + x509_data_xml(xml, client.x_509_certificate(:x)) + xml.AuthenticationVersion 'X002' + } + xml.EncryptionPubKeyInfo { + x509_data_xml(xml, client.x_509_certificate(:e)) + xml.EncryptionVersion 'E002' + } + xml.PartnerID partner_id + xml.UserID user_id + } + end.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML, encoding: 'utf-8') + end + 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, root_attributes) { xml.parent.add_child(header) xml.parent.add_child(body) } diff --git a/lib/epics/hpb.rb b/lib/epics/hpb.rb index 48d1abe0..b6dc20e8 100644 --- a/lib/epics/hpb.rb +++ b/lib/epics/hpb.rb @@ -8,8 +8,10 @@ def header nonce: nonce, timestamp: timestamp, order_type: 'HPB', + admin_order_type: 'HPB', order_attribute: 'DZHNN', with_bank_pubkey_digests: false, + with_order_params: false, mutable: {} ) end diff --git a/lib/epics/ini.rb b/lib/epics/ini.rb index 40e7ea16..c5d79bc5 100644 --- a/lib/epics/ini.rb +++ b/lib/epics/ini.rb @@ -6,8 +6,10 @@ def root def header client.header_request.build( order_type: 'INI', + admin_order_type: 'INI', order_attribute: 'DZNNN', with_bank_pubkey_digests: false, + with_order_params: false, mutable: {}, ) end @@ -23,6 +25,8 @@ def body end def key_signature + return h005_key_signature if client.h005? + Nokogiri::XML::Builder.new do |xml| xml.SignaturePubKeyOrderData('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'http://www.ebics.org/S001') { xml.SignaturePubKeyInfo { @@ -42,9 +46,25 @@ def key_signature end.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML, encoding: 'utf-8') end + # EBICS 3.0 (H005): the signature namespace is S002 and the public key is + # carried exclusively as an X.509 certificate (ds:X509Data) — the H004 + # RSAKeyValue is no longer part of the structure. + def h005_key_signature + Nokogiri::XML::Builder.new do |xml| + xml.SignaturePubKeyOrderData('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'http://www.ebics.org/S002') { + xml.SignaturePubKeyInfo { + x509_data_xml(xml, client.x_509_certificate(:a)) + xml.SignatureVersion 'A006' + } + xml.PartnerID partner_id + xml.UserID user_id + } + end.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML, encoding: 'utf-8') + end + 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, root_attributes) { xml.parent.add_child(header) xml.parent.add_child(body) } diff --git a/lib/epics/response.rb b/lib/epics/response.rb index 7ac98736..0126fefe 100644 --- a/lib/epics/response.rb +++ b/lib/epics/response.rb @@ -7,6 +7,12 @@ def initialize(client, xml) self.client = client end + # EBICS protocol namespace of the configured client (H004 by default, H005 + # when opted in). All response XPaths are scoped to this namespace. + def ns + client ? client.namespace : Epics::EBICS_PROTOCOLS.fetch(Epics::DEFAULT_VERSION)[:namespace] + end + def technical_error? !["011000", "000000"].include?(technical_code) end @@ -16,7 +22,7 @@ def technical_code end def mutable_return_code - doc.xpath("//xmlns:header/xmlns:mutable/xmlns:ReturnCode", xmlns: "urn:org:ebics:H004").text + doc.xpath("//xmlns:header/xmlns:mutable/xmlns:ReturnCode", xmlns: ns).text end def system_return_code @@ -28,7 +34,7 @@ def business_error? end def business_code - doc.xpath("//xmlns:body/xmlns:ReturnCode", xmlns: "urn:org:ebics:H004").text + doc.xpath("//xmlns:body/xmlns:ReturnCode", xmlns: ns).text end def ok? @@ -36,29 +42,29 @@ def ok? end def last_segment? - !!doc.at_xpath("//xmlns:header/xmlns:mutable/*[@lastSegment='true']", xmlns: "urn:org:ebics:H004") + !!doc.at_xpath("//xmlns:header/xmlns:mutable/*[@lastSegment='true']", xmlns: ns) end def segmented? - !!doc.at_xpath("//xmlns:header/xmlns:mutable/xmlns:SegmentNumber", xmlns: "urn:org:ebics:H004") + !!doc.at_xpath("//xmlns:header/xmlns:mutable/xmlns:SegmentNumber", xmlns: ns) end def return_code - doc.xpath("//xmlns:ReturnCode", xmlns: "urn:org:ebics:H004").last.content + doc.xpath("//xmlns:ReturnCode", xmlns: ns).last.content rescue NoMethodError nil end def report_text - doc.xpath("//xmlns:ReportText", xmlns: "urn:org:ebics:H004").first.content + doc.xpath("//xmlns:ReportText", xmlns: ns).first.content end def transaction_id - doc.xpath("//xmlns:header/xmlns:static/xmlns:TransactionID", xmlns: 'urn:org:ebics:H004').text + doc.xpath("//xmlns:header/xmlns:static/xmlns:TransactionID", xmlns: ns).text end def order_id - doc.xpath("//xmlns:header/xmlns:mutable/xmlns:OrderID", xmlns: "urn:org:ebics:H004").text + doc.xpath("//xmlns:header/xmlns:mutable/xmlns:OrderID", xmlns: ns).text end def digest_valid? @@ -78,13 +84,13 @@ def signature_valid? end def public_digest_valid? - encryption_pub_key_digest = doc.xpath("//xmlns:EncryptionPubKeyDigest", xmlns: 'urn:org:ebics:H004').first + encryption_pub_key_digest = doc.xpath("//xmlns:EncryptionPubKeyDigest", xmlns: ns).first client.e.public_digest == encryption_pub_key_digest.content end def order_data - order_data_encrypted = Base64.decode64(doc.xpath("//xmlns:OrderData", xmlns: 'urn:org:ebics:H004').first.content) + order_data_encrypted = Base64.decode64(doc.xpath("//xmlns:OrderData", xmlns: ns).first.content) data = (cipher.update(order_data_encrypted) + cipher.final) @@ -101,7 +107,7 @@ def cipher end def transaction_key - transaction_key_encrypted = Base64.decode64(doc.xpath("//xmlns:TransactionKey", xmlns: 'urn:org:ebics:H004').first.content) + transaction_key_encrypted = Base64.decode64(doc.xpath("//xmlns:TransactionKey", xmlns: ns).first.content) @transaction_key ||= client.e.key.private_decrypt(transaction_key_encrypted) end diff --git a/lib/epics/x_509_certificate.rb b/lib/epics/x_509_certificate.rb index 1eacaed6..1143d617 100644 --- a/lib/epics/x_509_certificate.rb +++ b/lib/epics/x_509_certificate.rb @@ -3,13 +3,43 @@ class Epics::X509Certificate attr_reader :certificate - def_delegators :certificate, :issuer, :version + def_delegators :certificate, :issuer, :version, :serial def initialize(crt_content) - @certificate = OpenSSL::X509::Certificate.new(crt_content) + @certificate = + if crt_content.is_a?(OpenSSL::X509::Certificate) + crt_content + else + OpenSSL::X509::Certificate.new(crt_content) + end end def data Base64.strict_encode64(@certificate.to_der) end -end \ No newline at end of file + + # SHA-256 fingerprint of the DER-encoded certificate (upper-case hex), as + # printed on the EBICS 3.0 INI letter. + def fingerprint + Digest::SHA256.hexdigest(@certificate.to_der).upcase + end + + # Generates a self-signed X.509 certificate wrapping the given RSA key. EBICS + # 3.0 (H005) requires every public key to be transmitted as an X.509 + # certificate; for banks using the shared-key model (e.g. German banks) a + # self-signed certificate is sufficient. + def self.generate_self_signed(rsa_key, subject:, valid_years: 10) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = OpenSSL::BN.new(Digest::SHA256.hexdigest(rsa_key.n.to_s(16))[0, 16].to_i(16)) + name = OpenSSL::X509::Name.parse(subject) + cert.subject = name + cert.issuer = name + cert.public_key = rsa_key.public_key + cert.not_before = Time.now + cert.not_after = Time.now + (valid_years * 365 * 24 * 60 * 60) + cert.sign(rsa_key, OpenSSL::Digest::SHA256.new) + + new(cert) + end +end diff --git a/spec/btf_spec.rb b/spec/btf_spec.rb new file mode 100644 index 00000000..294fbafb --- /dev/null +++ b/spec/btf_spec.rb @@ -0,0 +1,36 @@ +RSpec.describe Epics::BTF do + it 'normalizes attributes into the service hash consumed by HeaderRequest' do + btf = described_class.new( + service_name: 'SCT', scope: 'DE', service_option: 'URG', + container: 'ZIP', msg_name: 'pain.001', msg_version: '03', msg_variant: '001' + ) + + expect(btf.to_h).to eq( + service_name: 'SCT', + service_option: 'URG', + scope: 'DE', + container: 'ZIP', + msg_name: { name: 'pain.001', version: '03', variant: '001', format: nil } + ) + end +end + +RSpec.describe Epics::BtfMapping do + it 'maps a known upload code to a BTF' do + btf = described_class.upload('CCT') + expect(btf).to be_a(Epics::BTF) + expect(btf.service_name).to eq('SCT') + expect(btf.msg_name).to eq('pain.001') + end + + it 'maps a known download code to a BTF' do + btf = described_class.download('C53') + expect(btf.service_name).to eq('EOP') + expect(btf.container).to eq('ZIP') + expect(btf.msg_name).to eq('camt.053') + end + + it 'raises a helpful error for unmapped codes' do + expect { described_class.upload('ZZZ') }.to raise_error(ArgumentError, /raw/) + end +end diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb new file mode 100644 index 00000000..4faf044b --- /dev/null +++ b/spec/h005_client_spec.rb @@ -0,0 +1,112 @@ +RSpec.describe 'EBICS 3.0 (H005) client' do + let(:client) do + Epics::Client.new( + File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')), + 'secret', 'https://example.com', 'SIZBN001', 'EBIX', 'EBICS', + version: :h005 + ) + end + + describe 'version wiring' do + it { expect(client.ebics_version).to eq(:h005) } + it { expect(client.namespace).to eq('urn:org:ebics:H005') } + it { expect(client.protocol_version).to eq('H005') } + it { expect(client).to be_h005 } + + it 'defaults to H004 when no version is given' do + c = Epics::Client.new(nil, 'secret', 'https://example.com', 'H', 'U', 'P') + expect(c.ebics_version).to eq(:h004) + expect(c.namespace).to eq('urn:org:ebics:H004') + expect(c).not_to be_h005 + end + + it 'rejects unknown versions' do + expect { + Epics::Client.new(nil, 'secret', 'https://example.com', 'H', 'U', 'P', version: :h006) + }.to raise_error(ArgumentError, /Unsupported EBICS version/) + end + end + + describe 'admin order types' do + it { expect(Epics::HPB.new(client).to_xml).to include('HPB') } + it { expect(Epics::HTD.new(client).to_xml).to include('HTD') } + it { expect(Epics::HPB.new(client).to_xml).not_to include('OrderAttribute') } + + context 'XSD validity', if: ebics_xsd_available?(:h005) do + %i[HPB HTD HAA HKD HPD].each do |ot| + it "#{ot} is a valid H005 document" do + expect(Epics.const_get(ot).new(client).to_xml).to be_a_valid_ebics_doc(:h005) + end + end + + it 'INI is a valid H005 document' do + expect(Epics::INI.new(client).to_xml).to be_a_valid_ebics_doc(:h005) + end + + it 'HIA is a valid H005 document' do + expect(Epics::HIA.new(client).to_xml).to be_a_valid_ebics_doc(:h005) + end + end + end + + describe 'key management embeds X.509 certificates (mandatory in H005)' do + it 'embeds a self-signed cert in HIA order data' do + data = Epics::HIA.new(client).order_data + expect(data).to include('urn:org:ebics:H005') + expect(data).to include('X509Certificate') + end + + it 'embeds a self-signed cert in the INI signature order data' do + expect(Epics::INI.new(client).key_signature).to include('X509Certificate') + end + + it 'generates a certificate for each key type' do + %i[a x e].each { |t| expect(client.x_509_certificate(t)).to be_a(Epics::X509Certificate) } + end + + it 'uses the S002 signature namespace and no RSAKeyValue' do + sig = Epics::INI.new(client).key_signature + expect(sig).to include('http://www.ebics.org/S002') + expect(sig).not_to include('RSAKeyValue') + end + + context 'S002 XSD validity', if: ebics_xsd_available?(:h005) do + let(:s002) do + Nokogiri::XML::Schema(File.open(File.join(File.dirname(__FILE__), 'xsd', 'ebics_signature_S002.xsd'))) + end + + it 'INI signature order data validates against S002' do + errors = s002.validate(Nokogiri::XML(Epics::INI.new(client).key_signature)) + expect(errors).to be_empty + end + end + end + + describe 'convenience methods route to BTU/BTD under H005' do + it 'CCT builds a BTU with the SCT service' do + order = Epics::BTU.new(client, '', service: Epics::BtfMapping.upload('CCT')) + expect(order.header.to_s).to include('BTU') + expect(order.header.to_s).to include('SCT') + end + end +end + +RSpec.describe 'EBICS 2.5 (H004) remains the default and unchanged' do + let(:client) do + Epics::Client.new( + File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')), + 'secret', 'https://example.com', 'SIZBN001', 'EBIX', 'EBICS' + ) + end + + it 'still emits the classic OrderType/OrderAttribute shape' do + header = Epics::CCT.new(client, '').header.to_s + expect(header).to include('CCT') + expect(header).to include('OZHNN') + expect(header).not_to include('AdminOrderType') + end + + it 'does not auto-embed X.509 certificates' do + expect(Epics::HIA.new(client).order_data).not_to include('X509Certificate') + end +end diff --git a/spec/orders/btd_spec.rb b/spec/orders/btd_spec.rb new file mode 100644 index 00000000..5d0d6e50 --- /dev/null +++ b/spec/orders/btd_spec.rb @@ -0,0 +1,42 @@ +RSpec.describe Epics::BTD do + let(:client) do + Epics::Client.new( + File.open(File.join(File.dirname(__FILE__), '..', 'fixtures', 'SIZBN001.key')), + 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS', + version: :h005 + ) + end + + let(:service) do + Epics::BTF.new(service_name: 'EOP', scope: 'DE', container: 'ZIP', msg_name: 'camt.053', msg_version: '08') + end + + subject(:order) { described_class.new(client, service: service, from: '2026-01-01', to: '2026-01-31') } + + describe 'H005 envelope' do + it { expect(order.to_xml).to include('xmlns="urn:org:ebics:H005"') } + it { expect(order.to_xml).to include('Version="H005"') } + end + + describe 'OrderDetails / BTF' do + let(:header) { order.header.to_s } + + it { expect(header).to include('BTD') } + it { expect(header).to include('') } + it { expect(header).to include('EOP') } + it { expect(header).to include('DE') } + it { expect(header).to include('') } + it { expect(header).to include('camt.053') } + it { expect(header).to match(%r{\s*2026-01-01\s*2026-01-31\s*}) } + it { expect(header).not_to include('OrderAttribute') } + end + + describe '#to_xml' do + specify { expect(order.to_xml).to be_a_valid_ebics_doc(:h005) } if ebics_xsd_available?(:h005) + end + + describe '#to_receipt_xml' do + before { order.transaction_id = SecureRandom.hex(16) } + it { expect(order.to_receipt_xml).to include('xmlns="urn:org:ebics:H005"') } + end +end diff --git a/spec/orders/btu_spec.rb b/spec/orders/btu_spec.rb new file mode 100644 index 00000000..66089032 --- /dev/null +++ b/spec/orders/btu_spec.rb @@ -0,0 +1,59 @@ +RSpec.describe Epics::BTU do + let(:client) do + Epics::Client.new( + File.open(File.join(File.dirname(__FILE__), '..', 'fixtures', 'SIZBN001.key')), + 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS', + version: :h005 + ) + end + + let(:document) { File.read(File.join(File.dirname(__FILE__), '..', 'fixtures', 'xml', 'cd1.xml')) } + let(:service) do + Epics::BTF.new(service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_version: '03') + end + + subject(:order) { described_class.new(client, document, service: service) } + + describe 'H005 envelope' do + it { expect(order.to_xml).to include('xmlns="urn:org:ebics:H005"') } + it { expect(order.to_xml).to include('Version="H005"') } + end + + describe 'OrderDetails / BTF' do + let(:header) { order.header.to_s } + + it { expect(header).to include('BTU') } + it { expect(header).to include('') } + it { expect(header).to include('SCT') } + it { expect(header).to include('pain.001') } + it { expect(header).to match(%r{}) } + it { expect(header).not_to include('OrderAttribute') } + end + + describe 'signature flag' do + it 'omits SignatureFlag when the order carries no ES' do + order = described_class.new(client, document, service: service, signature_flag: false) + expect(order.header.to_s).not_to include('SignatureFlag') + end + + it 'adds requestEDS for distributed signature' do + order = described_class.new(client, document, service: service, request_eds: true) + expect(order.header.to_s).to include('requestEDS="true"') + end + end + + describe 'H005 upload body' do + it 'includes a DataDigest with the A006 signature version' do + expect(order.to_xml).to match(%r{.+}) + end + end + + describe '#to_xml' do + specify { expect(order.to_xml).to be_a_valid_ebics_doc(:h005) } if ebics_xsd_available?(:h005) + end + + describe '#to_transfer_xml' do + before { order.transaction_id = SecureRandom.hex(16) } + it { expect(order.to_transfer_xml).to include('xmlns="urn:org:ebics:H005"') } + end +end diff --git a/spec/support/ebics_matcher.rb b/spec/support/ebics_matcher.rb index 86c9bef6..44499fcd 100644 --- a/spec/support/ebics_matcher.rb +++ b/spec/support/ebics_matcher.rb @@ -1,22 +1,38 @@ -RSpec::Matchers.define :be_a_valid_ebics_doc do +# Root schema per EBICS protocol version. Drop the official H005 schema set into +# spec/xsd/ (ebics_H005.xsd + its includes) to enable H005 validation. +EBICS_XSD_ROOT = { + h004: 'ebics_H004.xsd', + h005: 'ebics_H005.xsd', +}.freeze + +def ebics_xsd_path(version) + File.join(File.dirname(__FILE__), '..', 'xsd', EBICS_XSD_ROOT.fetch(version)) +end + +# True when the XSD schema set for the given EBICS version is available locally. +def ebics_xsd_available?(version) + File.exist?(ebics_xsd_path(version)) +end + +RSpec::Matchers.define :be_a_valid_ebics_doc do |version = :h004| ## # use #open instead of #read to have the includes working # http://stackoverflow.com/questions/11996326/nokogirixmlschema-syntaxerror-on-schema-load/22971456#22971456 - def xsd - @xsd ||= Nokogiri::XML::Schema(File.open( File.join( File.dirname(__FILE__), '..', 'xsd', 'ebics_H004.xsd') )) + def xsd(version) + @xsd ||= Nokogiri::XML::Schema(File.open(ebics_xsd_path(version))) end match do |actual| - xsd.valid?(Nokogiri::XML(actual)) + xsd(version).valid?(Nokogiri::XML(actual)) end failure_message do |actual| - "expected that #{actual} would be a valid EBICS doc:\n\n #{xsd.validate(Nokogiri::XML(actual))}" + "expected that #{actual} would be a valid EBICS doc:\n\n #{xsd(version).validate(Nokogiri::XML(actual))}" end description do "be a valid EBICS document" end -end \ No newline at end of file +end diff --git a/spec/xsd/ebics_H005.xsd b/spec/xsd/ebics_H005.xsd new file mode 100644 index 00000000..149bf263 --- /dev/null +++ b/spec/xsd/ebics_H005.xsd @@ -0,0 +1,11 @@ + + + + ebics_H005.xsd inkludiert alle Schemadateien des EBICS-Protokolls, um die Eindeutigkeit von Element- und Typnamen im EBCIS Namespace zu erzwingen. + ebics_H005.xsd includes all schema files for the EBICS protocol in order to enforce unique element and type names in the EBICS namespace. + + + + + + diff --git a/spec/xsd/ebics_keymgmt_request_H005.xsd b/spec/xsd/ebics_keymgmt_request_H005.xsd new file mode 100644 index 00000000..cb93d3c4 --- /dev/null +++ b/spec/xsd/ebics_keymgmt_request_H005.xsd @@ -0,0 +1,523 @@ + + + + + ebics_keymgmt_request_H005.xsd ist das EBICS-Protokollschema für Schlüsselmanagement-Anfragen (HIA, HPB, HSA, INI). + + + + XML-Signature. + + + + + + + Datentyp für den statischen EBICS-Header (allgemein). + + + + + Hostname des Banksystems. + + + + + Zufallswert; damit wird die Initialisierungsnachricht des Clients einzigartig; nur anzugeben, falls Authentifikationssignatur vorhanden. + + + + + aktueller Zeitstempel zur Begrenzung der serverseitigen Nonce-Speicherung; nur anzugeben, falls Authentifikationssignatur vorhanden. + + + + + Kunden-ID des serverseitig administrierten Kunden. + + + + + Teilnehmer-ID des serverseitig zu diesem Kunden administrierten Teilnehmers. + + + + + technische User-ID für Multi-User-Systeme. + + + + + Kennung des Kundenprodukts bzw. Herstellerkennung oder Name. + + + + + Auftragsdetails. + + + + + Angabe des Sicherheitsmediums, das der Kunde verwendet. + + + + + + + + Datentyp für OrderDetails im statischen EBICS-Header (allgemein). + + + + + Auftragsart. + + + + + + + Datentyp für Element mit Kennung des Kundenprodukts bzw. Herstellerkennung oder Name. + + + + + + Sprachkennzeichen der Kundenproduktversion (gemäß ISO 639). + + + + + Kennung des Herausgebers des Kundenprodukts bzw. des betreuenden Kreditinstituts. + + + + + + + + Datentyp für den leeren variablen EBICS-Header von Key Managemen Aufträgen. + + + + + + + + Anfragestruktur für ungesicherte Auftragsarten HIA (Authentifikations- und Verschlüsselungsschlüssel senden) und INI (bankfachllichen Schlüssel senden). + + + + + + enthält die technischen Transaktionsdaten. + + + + + + enhält alle festen Headereinträge. + + + + + enthält alle variablen Headereinträge. + + + + + + + + + enthält die Auftragsdaten. + + + + + + + + + Transfer von Auftragsdaten. + + + + + + enthält Auftragsdaten. + + + + + + + + + + + + + + + + + + + + + + Datentyp für den statischen EBICS-Header bei ungesicherten Sendeauftragsarten (Aufträge HIA und INI): kein Nonce, kein Timestamp, keine EU-Datei, keine X001 Authentifizierung, keine Verschlüsselung, keine Digests der öffentlichen Bankschlüssel, Nutzdaten komprimiert + + + + + + + Hostname des Banksystems. + + + + + Zufallswert; damit wird die Initialisierungsnachricht des Clients einzigartig; nicht anzugeben für ebicsUnsecuredRequest. + + + + + aktueller Zeitstempel zur Begrenzung der serverseitigen Nonce-Speicherung; nicht anzugeben für ebicsUnsecuredRequest. + + + + + Kunden-ID des serverseitig administrierten Kunden. + + + + + Teilnehmer-ID des serverseitig zu diesem Kunden administrierten Teilnehmers. + + + + + technische User-ID für Multi-User-Systeme. + + + + + Kennung des Kundenprodukts bzw. Herstellerkennung oder Name. + + + + + Auftragsdetails. + + + + + Angabe des Sicherheitsmediums, das der Kunde verwendet. + + + + + + + + + + Datentyp für OrderDetails im statischen EBICS-Header von ebicsUnsecuredRequest. + + + + + + + Auftragsart. + + + + + + + + + Anfragestruktur für Auftragsarten ohne Übertragung der Digests der öffentlichen Bankschlüssel (HPB Bankschlüssel abholen). + + + + + + enthält die technischen Transaktionsdaten. + + + + + + enhält alle festen Headereinträge. + + + + + enthält alle variablen Headereinträge. + + + + + + + + + Authentifikationssignatur. + + + + + enthält optionale Zertifikate (vorgesehen). + + + + + + + + + X.509-Daten des Teilnehmers. + + + + + + + + + + + + Datentyp für den statischen EBICS-Header bei Aufträgen ohne Übertragung der Digests der Bankschlüssel (Auftrag HBP): keine Digests der öffentlichen Bankschlüssel, keine EU-Datei, keine Nutzdaten, OrderId optional!, Nonce, Timestamp, X001 Authentifizierung, Auftragsattribut DZHNN + + + + + + + Hostname des Banksystems. + + + + + Zufallswert; damit wird die Initialisierungsnachricht des Clients einzigartig. + + + + + aktueller Zeitstempel zur Begrenzung der serverseitigen Nonce-Speicherung. + + + + + Kunden-ID des serverseitig administrierten Kunden. + + + + + Teilnehmer-ID des serverseitig zu diesem Kunden administrierten Teilnehmers. + + + + + technische User-ID für Multi-User-Systeme. + + + + + Kennung des Kundenprodukts bzw. Herstellerkennung oder Name. + + + + + Auftragsdetails. + + + + + Angabe des Sicherheitsmediums, das der Kunde verwendet. + + + + + + + + + + Datentyp für OrderDetails im statischen EBICS-Header von ebicsNoPubKeyDigestsRequest. + + + + + + + Auftragsart. + + + + + + + + + The structure for uploads contains order data and the ESs, but without an authentication signature and data digest of bank keys. + Anfragestruktur für Sendeaufträge mit EU-Datei und Nutzdaten aber ohne Authentifizierungssignatur und Digests der Bankschlüssel. + + + + + + Contains technical transaction data. + enthält die technischen Transaktionsdaten. + + + + + + Contains all fixed header entries. + enhält alle festen Headereinträge. + + + + + Contains all mutable header entries. + enthält alle variablen Headereinträge. + + + + + + + + + Contains the order data and the ESs. + enthält die Auftragsdaten und EUs. + + + + + + + + + Transfer of order data and the ESs. + Transfer von Auftragsdaten und EUs. + + + + + + Contains the ESs. + enthält Signaturdaten (EUs). + + + + + + + + + + + + Contains the order data + enthält Auftragsdaten. + + + + + + + + + + + + + + + + + + + + + + Datentyp für den statischen EBICS-Header für ebicsUnsignedRequest.Datentyp für den statischen EBICS-Header bei Aufträgen ohne Authentifizierungssignatur (Auftrag HSA): keine X001 Authentifizierung, keine Digests der öffentlichen Bankschlüssel, EU-Datei, Nutzdaten, Nonce, Timestamp, OrderId, Auftragsattribut OZNNN + + + + + + + Hostname des Banksystems. + + + + + Zufallswert; damit wird die Initialisierungsnachricht des Clients einzigartig; nicht anzugeben bei ebicsUnsignedRequest. + + + + + aktueller Zeitstempel zur Begrenzung der serverseitigen Nonce-Speicherung; nicht anzugeben bei ebicsUnsignedRequest. + + + + + Kunden-ID des serverseitig administrierten Kunden. + + + + + Teilnehmer-ID des serverseitig zu diesem Kunden administrierten Teilnehmers. + + + + + technische User-ID für Multi-User-Systeme. + + + + + Kennung des Kundenprodukts bzw. Herstellerkennung oder Name. + + + + + Auftragsdetails. + + + + + Angabe des Sicherheitsmediums, das der Kunde verwendet. + + + + + + + + + + Datentyp für OrderDetails im statischen EBICS-Header von ebicsUnsignedRequest. + + + + + + + Auftragsart. + + + + + + + diff --git a/spec/xsd/ebics_keymgmt_response_H005.xsd b/spec/xsd/ebics_keymgmt_response_H005.xsd new file mode 100644 index 00000000..dfc33909 --- /dev/null +++ b/spec/xsd/ebics_keymgmt_response_H005.xsd @@ -0,0 +1,137 @@ + + + + + ebics_keymgmt_response_H005.xsd ist das EBICS-Protokollschema für Schlüsselmanagement-Antwortnachrichten (HIA, HPB, HSA, INI). + + + + XML-Signature. + + + + + + + Electronic Banking Internet Communication Standard des Zentralen Kreditausschusses (ZKA): Multibankfähige Schnittstelle zur internetbasierten Kommunikation. + + + + + + enthält die technischen Transaktionsdaten. + + + + + + enhält alle festen Headereinträge. + + + + + + + + enthält alle variablen Headereinträge. + + + + + + + + + enthält die Auftragsdaten und den fachlichen ReturnCode. + + + + + + Transfer von Auftragsdaten; nur bei Download anzugeben (HPB). + + + + + + Informationen zur Verschlüsselung der Auftragsdaten + + + + + + + + + + + + enthält Auftragsdaten. + + + + + + + + + + + + + + + + Antwortcode für den vorangegangenen Transfer. + + + + + + + + + + + + Zeitstempel der letzten Aktualisierung der Bankparameter; nur in der Initialisierungsphase anzugeben. + + + + + + + + + + + + + + + + + + + Datentyp für den variablen EBICS-Header. + + + + + Auftragsnummer von Sendeaufträgen gemäß DFÜ-Abkommen (used for all key management order types except download order type HPB). + + + + + Rückmeldung des Ausführungsstatus mit einer eindeutigen Fehlernummer. + + + + + Klartext der Rückmeldung des Ausführungsstatus. + + + + + + diff --git a/spec/xsd/ebics_orders_H005.xsd b/spec/xsd/ebics_orders_H005.xsd new file mode 100644 index 00000000..30db1213 --- /dev/null +++ b/spec/xsd/ebics_orders_H005.xsd @@ -0,0 +1,2094 @@ + + + + + ebics_orders_H005.xsd contains order-based reference elements and order-based type definitions for EBICS. + ebics_orders_H005.xsd enthält auftragsbezogene Referenzelemente und auftragsbezogene Typdefinitionen für EBICS. + + + + + + + XML-Klartext-Auftragsdaten für neue EBICS-Auftragsarten. + Order data in XML format for new EBICS order types. + + + + + Auftragsdaten für Auftragsart HAA (Antwort: abrufbare Auftragsarten abholen). + Order data for order type HAA (response: receive downloadable order types). + + + + + Auftragsdaten für Auftragsart HCA (Anfrage: Änderung der Teilnehmerschlüssel für Authentifikation und Verschlüsselung). + Order data for order type HCA (request: replace user's keys for authentication and encryption). + + + + + Auftragsdaten für Auftragsart HCS (Anfrage: Schlüsselwechsel aller Schlüssel). + Order data for order type HCS (request: replace all keys). + + + + + Auftragsdaten für Auftragsart HIA (Anfrage: Initialisierung der Teilnehmerschlüssel für Authentifikation und Verschlüsselung). + Order data for order type HIA (request: initialise user's keys for authentication and encryption). + + + + + Order data for order type H3K (request: initialise all three user's keys). + Auftragsdaten für Auftragsart H3K (Anfrage: Initialisierung aller drei Teilnehmerschlüssel). + + + + + Auftragsdaten für Auftragsart HKD (Antwort: Kunden- und Teilnehmerdaten des Kunden abholen). + Order data for order type HKD (response: receive customer-based information on the customer and the customer's users). + + + + Schlüssel zur Identifikation des Kontos. + Key for the identification of the account. + + + + + + + Referenz auf die Konten-Identifikationsschlüssel. + Reference to the account identification keys. + + + + + + + + Auftragsdaten für Auftragsart HPB (Antwort: Transfer der Bankschlüssel). + Order data for order type HPB (response: receive bank's public keys). + + + + + Auftragsdaten für Auftragsart HPD (Antwort: Bankparameter abholen). + Order data for order type HPD (response: receive bank parameters). + + + + + Auftragsdaten für Auftragsart HTD (Antwort: Kunden- und Teilnehmerdaten des Teilnehmers abholen). + Order data for order type HTD (response: receive user-based information on the user's customer and the user herself/himself). + + + + Schlüssel zur Identifikation des Kontos. + Key for the identification of the account. + + + + + + + Referenz auf die Konten-Identifikationsschlüssel. + Reference to the account identification keys. + + + + + + + + Auftragsdaten für Auftragsart HVD (Antwort: VEU-Status abrufen). + Order data for order type HVD (response: receive the status of an order currently stored in the distributed signature processing unit). + + + + + Auftragsdaten für Auftragsart HVS (Anfrage: VEU-Storno). + Order data for order type HVS (request: reject an order currently stored in the distributed signature processing unit). + + + + + Auftragsdaten für Auftragsart HVT (Antwort: VEU-Transaktionsdetails abrufen). + Order data for order type HVT (response: receive transaction details of an order currently stored in the distributed signature processing unit). + + + + + + + + + + Auftragsdaten für Auftragsart HVU (Antwort: VEU-Übersicht abholen). + Order data for order type HVU (response: receive summary of orders currently stored in the distributed signature processing unit). + + + + + Auftragsdaten für Auftragsart HVZ (Antwort: VEU-Übersicht mit Zusatzinformationen abholen). + Order data for order type HVZ (response: receive summary of orders currently stored in the distributed signature processing unit with additional information). + + + + + + XML-Strukturen für bankfachliche Elektronische Unterschriften (EUs). + contains the digital signatures. + + + + + enthält die EU des Kreditinstituts. + contains the digital signatures. + + + + + + zusätzliche Auftragsparameter, die zur Ausführung des Auftrags notwendig sind. + additional order parameters required to execute the order. + + + + + zusätzliche Auftragsparameter für Auftragsart HVD. + additional order parameters for order type HVD. + + + + + zusätzliche Auftragsparameter für Auftragsart HVE. + additional order parameters for order type HVE. + + + + + zusätzliche Auftragsparameter für Auftragsart HVS. + additional order parameters for order type HVS. + + + + + zusätzliche Auftragsparameter für Auftragsart HVT. + additional order parameters for order type HVT. + + + + + zusätzliche Auftragsparameter für Auftragsart HVU. + additional order parameters for order type HVU. + + + + + zusätzliche Auftragsparameter für Auftragsart HVZ. + additional order parameters for order type HVZ. + + + + + zusätzliche Auftragsparameter für Standard-Auftragsarten. + additional order parameters for standard order types. + + + + + + Standard-Requeststruktur für HVx-Aufträge (HVD, HVT, HVE, HVS). + Standard request structure for HVx orders (HVD, HVT, HVE, HVS). + + + + Standard-Requestdaten. + Standard request data. + + + + Kunden-ID des Einreichers des ausgewählten Auftrags. + Customer ID of the presenter of the selected order. + + + + + BTF Service Parameter struktur im Falle von BTU/BTD + Identification of the file format in the case of FUL/FDL + + + + + Auftragsnummer des ausgewählten Auftrags. + Order ID of the selected order. + + + + + + + Marker für Elemente und deren Substrukturen, die authentifiziert werden sollen. + Marker for elements and their substructures that are to be authenticated. + + + + Das zugehörige Element ist mitsamt seinen Unterstrukturen zu authentifizieren. + The element (and its substructures) that belongs to this attribute is to be authenticated. + + + + + + optionales Support-Flag, Default = true. + optional support flag, default = true. + + + + Wird die Funktion unterstützt? + Is this function supported? + + + + + + + EU-Berechtigungsinformationen. + permission information of a user's digital signature. + + + + Unterschriftsberechtigung des Teilnehmers, der unterzeichnet hat. + Authorisation level of the user that signed the order. + + + + + + + + Datentyp für Signaturdaten des Kreditinstituts beim EU-Transfer. + Data type for digital signature data transferred using EBICS. + + + + + bankfachliche Elektronische Unterschrift. + Digital signature (either autorising an order or applied for transportation). + + + + + + + + + + + + + Datentyp für Vorabprüfung (Anfrage). + Data type for pre-validation (request). + + + + Client sendet den Hashwert der Auftragsdaten und alle weiteren Daten, die er im Rahmen der Vorabprüfung zur Verfügung stellen will + + + + Hashwert der zu übertragenden Auftragsdatendatei für die Vorabprüfung. + Hashvalue of the transmitted order data for the prevalidation. + + + + + Kontoangabe zur Kontoberechtigung für diesen Zahlungsverkehrsauftrag bei der Vorabprüfung. + Account information for authorisation checks for the payment order within the prevalidation. + + + + + + + + Datentyp für Kontenberechtigungsdaten zur Vorabprüfung. + Data type for the account authorisation data for the prevalidation. + + + + + + + Summe der Zahlungsverkehrsaufträge dieses Kontos für die Höchstbetragsprüfung der EU. + Total sum of the ordered payments regarding this account in order to check the maximum amount limit of the signature permission grades. + + + + + + + + + Datentyp für den Transfer von Auftragsdaten (Anfrage). + Data type for the transfer of order data (request). + + + + + Transaktionsphase? + + + + Initialisierungsphase: Transfer der Signaturdaten (EUs) und des Transaktionsschlüssels. + Inituialisation phase: Transfer of signatur data (ESs) and transaktion key. + + + + Information zur Verschlüsselung der Signatur- und Auftragsdaten. + Information regarding the encryption of signature and order data. + + + + + + + + + + + + enthält Signaturdaten (EUs). + contains signature data (ESs). + + + + + + + + + + + + Hashwert der Auftragsdaten. + Hashvalue of the order data. + + + + + Additional Information about the order (unstructured, up to 255 characters). + + + + + + Transferphase: Transfer von Auftragsdaten. + Transferphase: Transfer of order data. + + + + enthält Auftragsdaten. + contains order data. + + + + + + + + + + + + + + + + + Datentyp für den Transfer von Auftragsdaten (Antwort). + + + + + Transfer des Sitzungsschlüssels und (optional) der Signaturdaten (EUs); nur in der Initialisierungsphase anzugeben. + Transfer of the session key and (optional) signature data (ESs); to be specified only in the initialisation phase. + + + + Information zur Verschlüsselung der Signatur- und Auftragsdaten. + Information regarding the encryption of signature and order data. + + + + + + + + + + + + enthält Signaturdaten (EUs). + contains signature data (ESs). + + + + + + + + + + + + + enthält Auftragsdaten. + contains order data. + + + + + + + + + + + + + + + Datentyp für den Transfer von Transferquittungen. + Data type for the transfer of transfer receipts. + + + + + Quittierungscode für Auftragsdatentransfer. + Receipt code fpr transfer of order data. + + + + + + + + Datentyp für den Transfer von Antwortcodes. + + + + + Antwortcode für den vorangegangenen Transfer. + response code for the foregoing transfer. + + + + + Zeitstempel der letzten Aktualisierung der Bankparameter. + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HAA (Antwort: abrufbare Auftragsarten abholen). + Data type for order data of order type HAA (Response: Download of available order data). + + + + + Liste von Auftragsarten, für die Daten bereit stehen. + List of order types for which data are available. + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HCA (Anfrage: Änderung der Teilnehmerschlüssel für Authentifikation und Verschlüsselung). + Data type for order data regarding order type HCA (Request: Update of Subscriber's key for authentication and encryption). + + + + + öffentlicher Authentifikationsschlüssel. + public key for authentication. + + + + + öffentlicher Verschlüsselungsschlüssel. + public key for encryption. + + + + + Kunden-ID. + Partner-ID. + + + + + Teilnehmer-ID. + User-ID. + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HCS (Anfrage: Schlüsselwechsel aller Schlüssel). + Data type for order data for order type HCS (Request: Update of all keys). + + + + + öffentlicher Authentifikationsschlüssel. + public key for authentication. + + + + + öffentlicher Verschlüsselungsschlüssel. + public key for encryption. + + + + + + Kunden-ID. + Partner-ID. + + + + + Teilnehmer-ID. + User-ID. + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HIA (Anfrage: Initialisierung der Teilnehmerschlüssel für Authentifikation und Verschlüsselung). + Data type for order data for order type HIA (Request: Initialisation of subcriber keys for authentication and encryption). + + + + + öffentlicher Authentifikationsschlüssel. + public key for authentication. + + + + + öffentlicher Verschlüsselungsschlüssel. + public key for encryption. + + + + + Kunden-ID. + Partner-ID. + + + + + Teilnehmer-ID. + User-ID. + + + + + + + + Datentyp für Auftragsdaten für Auftragsart H3K (Anfrage: Initialisierung aller drei Teilnehmerschlüssel). + Order type for order data H3K (request: initialise all three user's keys). + + + + + Key for electronic Signature + Signaturschlüssel. + + + + + Authentication key + Authentifikationsschlüssel. + + + + + Encryption key + Verschlüsselungsschlüssel. + + + + + PartnerID. + Kunden-ID. + + + + + UserID. + Teilnehmer-ID. + + + + + + + Datentyp für Auftragsdaten für Auftragsart HKD (Antwort: Kunden- und Teilnehmerdaten des Kunden abholen). + Order data for order type HKD (response: receive customer based information on the customer and the customer's user. + + + + + Kundendaten. + Customer data. + + + + + Teilnehmerdaten. + User data. + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HPB (Antwort: Transfer der Bankschlüssel). + Data type for order data for order type HPB (Response: Transfer of bank keys). + + + + + öffentlicher Authentifikationsschlüssel. + public authentication key + + + + + öffentlicher Verschlüsselungsschlüssel. + public encryption key + + + + + öffentlicher EU-Signaturschlüssel. + public ES key. + + + + + Banksystem-ID. + Host-ID. + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HPD (Antwort: Bankparameter abholen). + Data type for order data for order type HPD (Response: Download bank parameters). + + + + + Zugangsparameter. + Access Parameter. + + + + + Protokollparameter. + Protocol Parameter. + + + + + + + Datentyp für HPD-Zugangsparameter. + data type for HPD Access Parameter. + + + + + institutsspezifische IP-Adresse/URL. + individual IP-address/URL of the bank. + + + + + + + Gültigkeitsbeginn für die angegebene URL/IP. + Valid-From-Date of the URL/IP. + + + + + + + + + Institutsbezeichnung. + Name of the bank. + + + + + + + + + + Banksystem-ID. + + + + + + + + Datentyp für HPD-Protokollparameter. + Data type for HPD's parameters regarding the EBICS protocol. + + + + + Spezifikation unterstützter Versionen. + Specification of supported versions.. + + + + + Parameter zur Recovery-Funktion (Wiederaufnahme abgebrochener Übertragungen). + Parameter denoting the recovery function (recovery of aborted transmissions). + + + + + + + + Parameter zur Vorabprüfung (über die Übermittlung der EU hinaus). + Parameter denoting the pre-validation (beyond transmission of signatures). + + + + Optionales Support-Flag, Default = true. + Optional support flag, default = true. + + + + + + + Parameter zum Download von Kunden- und Teilnehmerdaten (Auftragsarten HKD/HTD). + Parameter denoting the download of customer and user data (order types HKD/HTD). + + + + + + + + Parameter zum Abruf von Auftragsarten, zu denen Auftragsdaten verfügbar sind (Auftragsart HAA). + Parameter denoting the reception of order types which provides downloadable order data (order type HAA). + + + + + + + + + + + Datentyp für HPD-Versionsinformationen. + Data type for HPD version information. + + + + + unterstützte EBICS-Protokollversionen (H...). + supported EBICS protocol versions. (H...). + + + + + + + + unterstützte Versionen der Authentifikation (X...). + supported version for authentication (X...). + + + + + + + + unterstützte Versionen der Verschlüsselung (E...). + supported version for encryption (E...). + + + + + + + + unterstützte EU-Versionen (A...). + supported version for ES (A...). + + + + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HTD (Antwort: Kunden- und Teilnehmerdaten des Teilnehmers abholen). + Data type for order data for order type HTD (Response: Download partner- and user data). + + + + + Kundendaten. + Customer data. + + + + + Teilnehmerdaten. + User data. + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HVD (Antwort: VEU-Status abrufen). + Data type for order data for order type HVD (Response: EDS-status). + + + + + Hashwert der Auftragsdaten. + Hash value of the order data. + + + + + Begleitzettel/"Displaydatei" (entspricht der Dateianzeige im Kundenprotokoll gemäß DFÜ-Abkommen). + Accompanying ticket/"display file" (corresponds to the display file of the customer's journal according to the document "DFÜ-Abkommen"). + + + + + Kann die Auftragsdatei im Originalformat abgeholt werden? (HVT mit completeOrderData=true) + Can the order file be downloaded in the original format? (HVT with completeOrderData=true) + + + + + Größe der unkomprimierten Auftragsdaten in Bytes. + Size of the uncompressed order data (byte count). + + + + + Können die Auftragsdetails als XML-Dokument HVTResponseOrderData abgeholt werden? (HVT mit completeOrderData=false) + Can the order details be downloaded as XML document HVTResponseOrderData? (HVT with completeOrderData=false) + + + + + bankfachliche Elektronische Unterschrift des Kreditinstituts über Hashwert und Displaydatei. + Digital Signature issued by the bank, covering the hash value and the accompanying ticket. + + + + + Informationen zu den bisherigen Unterzeichnern. + Information about the already existing signers. + + + + + + + + Datentyp für zusätzliche Auftragsparameter für Auftragsart HVD. + Data type for additional order parameters for order type HVD. + + + + + + + + + Datentyp für zusätzliche Auftragsparameter für Auftragsart HVE. + Data type for additional order parameters for order type HVE. + + + + + + + + + Datentyp für zusätzliche Auftragsparameter für Auftragsart HVS. + Data type for additional order parameters for order type HVS. + + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HVS (Anfrage: VEU-Storno). + Data type for order data for order type HVS (request: EDS cancellation). + + + + + Hashwert der Auftragsdaten des stornierten Auftrags. + Hash value of order data of cancelled order. + + + + + + + + Datentyp für Antwort mit Einzelauftraginfos für Auftragsart HVT (Antwort VEU-Transaktionsdetails abrufen mit completeOrderData="false"). + Data type for a response containing information about single transactions for order type HVT (response: EDS transaction details with completeOrderData="false"). + + + + + Gesamtanzahl der Einzelaufträge für den Auftrag. + Total number of order infos for the order. + + + + + Einzelauftragsinfos. + Particular order content information requested for display matters. + + + + + + + + Datentyp für HVT-Konteninformationen. + Data type for account information regarding order type HVT. + + + + + + + + //MODIFIED - Replaced Element OrderFormat with MsgName// Datentyp für HVT-Auftragsinformationen. + + + + + + kontobezogene Details des Auftrags (Auftraggeber, Empfänger etc.). + account related details of the order (ordering party, receiver etc.). + + + + + Ausführungsdatum. + Execution date. + + + + + + + + + + Betrag. + Amount. + + + + + + + Gutschrift (isCredit = "true") oder Lastschrift (isCredit = "false")? + Credit (isCredit = "true") or debit (isCredit = "false")? + + + + + Währungscode. + Currency code. + + + + + + + + + Textfeld zur weiteren Beschreibung der Transaktion (Verwendungszweck, Auftragsdetails, Kommentar). + text field for additional descriptions regarding the transaction (remittance information, order details, annotations). + + + + + + + Beschreibungstyp. + Description type. + + + + + + Verwendungszweck + remittance information. + + + + + Auftragsdetails + Order details. + + + + + Kommentar + Annotation. + + + + + + + + + + + + + + + Datentyp für HVT-Auftragsflags. + Data type for HVT order flags. + + + + Sollen die Transaktionsdetails als Einzelauftragsinfos (completeOrderData=false) oder als komplette Originaldaten (completeOrderData=true) übertragen werden? (Vorschlag für Default=false) + Are the transaction details so be transmitted as particular order content information requested for display matters or in complete order data file form? (Proposal for Default=false) + + + + + Limit für die zu liefernden Transaktionsdetails, bei completeOrderData=false maximale Anzahl zu liefernder Einzelauftragsinfos, 0 für unbegrenzt (Vorschlag für Default=100). + Limit for the transaction details to be transmitted; if completeOrderData=false, maximum number of details of a particular order; 0 for unlimited number of details (Proposal for Default=100). + + + + + + + + + + Offset vom Anfang der Originalauftragsdatei für die zu liefernden Transaktionsdetails, bei completeOrderData=false bezogen auf laufende Nummer des Einzelauftrags (Vorschlag für Default=0). + Offset position in the original order file which marks the starting point for the transaction details to be transmitted; applies to the sequential number of a particular order if completeOrderData=false (Proposal for Default=0). + + + + + + + + + + + + Datentyp für zusätzliche Auftragsparameter für Auftragsart HVT. + Data type for additional order parameters for order type HVT. + + + + + + spezielle Flags für HVT-Aufträge. + Special order flags for orders of type HVT. + + + + + + + + + + Generische Schlüssel-Wert-Parameter + Generic key-value parameters + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HVU (Antwort: VEU-Übersicht abholen). + Data type for order data for order type HVU (Response: Download EDS overview). + + + + + + + + Auftragsinformationen. + + + + + + + + Datentyp für HVU-Auftragsdetails. + Data type for HVU order details. + + + + + Auftragsart lt. DFÜ-Abkommen des ausgewählten Auftrags. + Type of the order. + + + + + Auftragsnummer lt. DFÜ-Abkommen des ausgewählten Auftrags. + Order number. + + + + + Größe der unkomprimierten Auftragsdaten in Bytes. + Order data size in bytes. + + + + + Informationen zu den Unterschriftsmodalitäten. + Signing information. + + + + + Informationen zu den bisherigen Unterzeichnern. + Information regarding the signer. + + + + + Informationen zum Einreicher. + Information regarding the originator. + + + + + Additional Information about the order (unstructured, up to 255 characters). + Additional Information about the order (unstructured, up to 255 characters). + + + + + + + + Datentyp für zusätzliche Auftragsparameter für Auftragsart HVU. + Data type for additional order parameters for order type HVU. + + + + + Liste von Auftragsarten, für die zur Unterschrift vorliegende Aufträge abgerufen werden sollen; falls nicht angegeben, werden sämtliche für den Teilnehmer unterschriftsfähigen Aufträge abgerufen. + + + + + + + + Datentyp für zusätzliche Auftragsparameter für Auftragsart HVZ. + Data type for additional order parameters for order type HVZ. + + + + + Liste von Auftragsarten, für die zur Unterschrift vorliegende Aufträge abgerufen werden sollen; falls nicht angegeben, werden sämtliche für den Teilnehmer unterschriftsfähigen Aufträge abgerufen. + List of order types that the orders ready to be signed by the requesting user should match; if not specified, a list of all orders ready to be signed by the requesting user is returned. + + + + + + + + Datentyp für Informationen zu den HVU-Unterschriftsmodalitäten. + + + + Ist der Auftrag unterschriftsreif ("true") oder bereits vom Teilnehmer unterschrieben ("false")? + + + + + Anzahl der insgesamt zur Freigabe erforderlichen EUs. + + + + + Anzahl der bereits geleisteten EUs. + + + + + + Datentyp für Informationen zum Ersteller eines HVU-Auftrags. + + + + + Kunden-ID des Einreichers. + + + + + Teilnehmer-ID des Einreichers. + + + + + Name des Einreichers. + + + + + Zeitstempel der Einreichung (d.h. der Übertragung der Auftragsdatei). + + + + + + + + Datentyp für Auftragsdaten für Auftragsart HVZ (Antwort: VEU-Übersicht mit Zusatzinformationen abholen). + Order data for order type HVZ (response: receive summary of orders currently stored in the distributed signature processing unit with additional informations). + + + + + + + + Auftragsinformationen. + Summary of order information. + + + + + + + + Datentyp für HVZ-Auftragsdetails. + + + + + BTF Service Parameter-Struktur des ausgewählten Auftrags. + Type of the order. + + + + + Auftragsnummer lt. DFÜ-Abkommen des ausgewählten Auftrags. + ID number of the order. + + + + + Hashwert der Auftragsdaten. + Hash value of the order data. + + + + + Kann die Auftragsdatei im Originalformat abgeholt werden? (HVT mit completeOrderData=true). + Can the order file be downloaded in the original format? (HVT with completeOrderData=true) + + + + + Größe der unkomprimierten Auftragsdaten in Bytes. + Size of uncompressed order data in Bytes. + + + + + Können die Auftragsdetails als XML-Dokument HVTResponseOrderData abgeholt werden? (HVT mit completeOrderData=false). + Can the order details be downloaded as XML document HVTResponseOrderData? (HVT with completeOrderData=false) + + + + + Zusätzliche Auftragsdetails nur für Zahlungsaufträge. + Order details related to payment orders only. + + + + + Informationen zu den Unterschriftsmodalitäten. + Information regarding the signing modalities of the order. + + + + + Informationen zu den bisherigen Unterzeichnern. + Information regarding the users who already signed the order. + + + + + Informationen zum Einreicher. + Information regarding the originator of the order. + + + + + Additional Information about the order (unstructured, up to 255 characters). + + + + + + + + Standard-Requeststruktur für HVx-Aufträge (HVD, HVT, HVE, HVS). + Standard structure for HVZ OrderDetails related to payment orders + + + + + Anzahl der Zahlungssätze über alle logische Dateien entsprechend Dateianzeige. + Total transaction number for all logical files (from dispay file). + + + + + Summe der Beträge über alle logische Dateien entsprechend Dateianzeige. + Total transaction amount for all logical files (from dispay file). + + + + + + + Nur Gutschriften (isCredit = "true") oder nur Lastschriften (isCredit = "false")? Sonst keine Nutzung des Elements. + + + + + + + + + Auftragswährung (nur bei sortenreinen Zahlungen, sonst keine Angabe). + Order currency (only if identical across all transactions, ship otherwise). + + + + + Informationen aus Dateianzeige der ersten logischen Datei. + Order details from display file for first logical file. + + + + + + Auftraggeber entsprechend Dateianzeige. + Order party information (from display file). + + + + + Erstes Auftraggeberkonto entsprechend Dateianzeige. + First order party account (from display file). + + + + + + + Kontonummer (deutsches Format oder international als IBAN). + Account number (German format or international as IBAN). + + + + + + + Ist die Kontonummer im deutschen Format (international=false) oder im internationalen Format (international=true, IBAN) angegeben? + Account number given in German format (international=false) or in international format (international=true, IBAN)? + + + + + + + + + Kontonummer im freien Format. + Account number in free format. + + + + + + + Formatkennung. + Format type. + + + + + + + + + + + Bankleitzahl (deutsches Format oder international als SWIFT-BIC). + Bank sort code (German format or international as SWIFT-BIC). + + + + + + + Ist die Bankleitzahl im deutschen Format (international=false, BLZ) oder im internationalen Format (international=true, SWIFT-BIC) angegeben? + Bank sort code given in German format (international=false) or in international format (international=true, SWIFT-BIC)? + + + + + nationales Präfix für Bankleitzahlen. + National prefix for bank sort code. + + + + + + + + + Bankleitzahl im freien Format. + Bank sort code in free format. + + + + + + + Formatkennung. + Format type. + + + + + + + + + + + + + + + + + + Datentyp für Informationen zu einem Unterzeichner eines VEU-Auftrags (HVU, HVD). + + + + + Kunden-ID des Unterzeichners. + + + + + Teilnehmer-ID des Unterzeichners. + + + + + Name des Unterzeichners. + + + + + Zeitstempel der Unterzeichnung (d.h. der Übertragung der Unterschrift). + + + + + zusätzliche Informationen zu den Berechtigungen des Teilnehmers, der unterzeichnet hat. + + + + + + + + + + + Datentyp für VEU-Berechtigungsinformationen des Teilnehmers (HKD, HTD). + + + + + Liste von Auftragsarten, für die die Berechtigung des Teilnehmers gültig ist. + List of order types which the user's permission belongs to. + + + + + BTF Service Parameter struktur im Falle von BTU/BTD + Identification of the file format in the case of FUL/FDL + + + + + Verweis auf den Identifikationscode des berechtigten Kontos. + Identification codes of the affected accounts. + + + + + Betragshöchstgrenze, bis zu der die Berechtigung des Teilnehmers gültig ist. + Maximum total amount which the user's permission is valid for. + + + + + + + Unterschriftsklasse, für die der Teilnehmer berechtigt ist; nicht anzugeben bei Download-Auftragsarten. + Authorization level of the user who signed the order; to be omitted for orders of type "download". + + + + + + + Datentyp für VEU-Partnerdaten (HKD, HTD). + Data type for customer data with regard to distributed signatures (order types HKD, HTD). + + + + + Informationen zur Adresse des Kunden. + Information about the customer's adress. + + + + + Informationen zur Kreditinstitutsanbindung des Kunden. + Information about the customer's banking access paramters. + + + + + Informationen zu den Konten des Kunden. + Information about the customer's accounts. + + + + + + + + //MODIFIED//Liste der Auftragsartenbeschränkungen; falls nicht angegeben, gibt es keine Auftragsartenbeschränkungen; falls das Element ohne Service-Element geliefert wird, ist das Konto für keine Auftragsart freigegeben. + List containing the order types which contain this account is restricted to; if omitted, the account is unrestricted; if the list is empty the account is blocked for any order type. + + + + + + + Identifikationscode des Kontos. + + + + + + + + + Informationen zu den Auftragsarten, für die der Kunde berechtigt ist. + Information about order types which the customer is authorised to use. + + + + + + + Datentyp für VEU-Adressinformationen (HKD, HTD). + Data type for address information with regard to distributed signature (order types HKD, HTD). + + + + + Name des Kunden. + Customer's name. + + + + + Straße und Hausnummer. + Street and house number. + + + + + Postleitzahl. + Postal code. + + + + + Stadt. + City. + + + + + Region / Bundesland / Bundesstaat. + Region / province / federal state. + + + + + Land. + Country. + + + + + + + + Datentyp für VEU-Kreditinstitutsinformationen (HKD, HTD). + + + + + Banksystem-ID. + + + + + + + + + Datentyp für VEU-Teilnehmerinformationen (HKD, HTD). + + + + + Teilnehmer-ID. + + + + + + + Status des Teilnehmers. + + + + + + + + + Name des Teilnehmers. + + + + + Informationen zu den Berechtigungen des Teilnehmers. + + + + + + + + Datentyp für VEU-Berechtigungsinformationen zu Auftragsarten (HKD, HTD). + Data type for user permissions with regard to distributed signatures (order types HKD, HTD). + + + + + Administrative EBICS Auftragsart. + + + + + BTF Service Parameter struktur im Falle von BTU/BTD + Identification of the file format in the case of FUL/FDL + + + + + Beschreibung der Auftragsart. + + + + + Anzahl erforderlicher EUs (Default=0). + + + + + + + + Datentyp für zusätzliche Auftragsparameter bei Standard-Auftragsarten. + + + + + Datumsbereich (von-bis). + + + + + + Startdatum (inkl.). + + + + + Enddatum (inkl.). + + + + + + + + + + Attribute zur EBICS-Protokollversion und -revision. + Attributes regarding the protocol version and revision of EBICS. + + + + Version des EBICS-Protokolls (z.B. "H00x"). + Version of the EBICS protocol (e.g. "H00x"). + + + + + Revision des EBICS-Protokolls (z.B. 1). + Revision of the EBICS protocol (e.g. 1). + + + + + + + zusätzliche Auftragsparameter für Auftragsart BTD. + additional order parameters for order type BTD. + + + + + zusätzliche Auftragsparameter für Auftragsart BTU. + additional order parameters for order type BTU. + + + + + Datentyp für BTF Download Parameter + + + + + + + Service name - target system for the further processing of the order + + + + + + + + The file name on the client It can be transmitted optionally + + + + + + + + Datentyp für BTF Upload Parameter + + + + + + + Service name - target system for the further processing of the order + + + + + If not present the order doesn't contain any ES and shall be authorised outside EBICS +If present the order shall be autorised within EBICS: +1. If the attribute VEU is also present the sender desires spooling into the VEU - hence in this case the order is not rejected in the case of not sufficient number of ES +2. If the attribute is not present all necessary ES must be inside the order (else: rejection of the order) + + + + + + + The file name on the client It can be transmitted optionally + + + + + + + + Abstract Type containing all BTF params structures + + + + + Service name - target system for the further processing of the order + + + + + If not present the order doesn't contain any ES and shall be authorised outside EBICS +If present the order shall be autorised within EBICS: +1. If the attribute VEU is also present the sender desires spooling into the VEU - hence in this case the order is not rejected in the case of not sufficient number of ES +2. If the attribute is not present all necessary ES must be inside the order (else: rejection of the order) + + + + + + + + The file name on the client It can be transmitted optionally + + + + + + Datentyp für die Angabe eines (Berichts-) Zeitraums + + + + + + + + + Basis-Datentyp für Kennzeichen mit optionalem Attribut + + + + + + Datentyp für Meldungstyp-String mit optionalen Attributen + + + + + + Variant number of the message type (usable for ISO20022 messages) + + + + + Version number of the message type (usable for ISO20022 messages) + + + + + Encoding format of the message (e.g. XML, ASN1, JSON, PDF) + + + + + + + + + + + + + Basisdatentyp für BTF-Service Parameter Set + + + + + Service Code name: External list specified and maintained by EBICS. Basis is the "SWIFT-list" for the field "description" (SCT, DCT, XCT, SDD, DDD, STM, REP...) plus additional codes needed for further services + + + + + Specifies whose rules have to be taken into account for the service. This means which market / comminity has defined the rules. +If the element is absent a global definition for the service is assumed. +External list specified and maintained by EBICS. In addition the following codes may be used: +2-character ISO country code or a 3-character issuer code (defined by EBICS) + + + + + Service Option Code +Additional option for the service (also depends on used scope) + + + + + Container flag. If present, data is provided/requested in a container format specified in the attribute of the flag + + + + + Name of the message, e.g. pain.001 or mt101 National message names (issued by DK, CFONB or SIC are also allowed) + + + + + + + Type is arestriction of the generic ServiceType, defining the mandatory elements + + + + + + + Service Code name: External list specified and maintained by EBICS. Basis is the "SWIFT-list" for the field "description" (SCT, DCT, XCT, SDD, DDD, STM, REP...) plus additional codes needed for further services + + + + + Specifies whose rules have to be taken into account for the service. This means which market / comminity has defined the rules. +If the element is absent a global definition for the service is assumed. +External list specified and maintained by EBICS. +In addition the following codes may be used: +2-character ISO country code or a 3-character issuer code (defined by EBICS) + + + + + Service Option Code + Additional option for the service (also depends on used scope) + + + + + Container flag. If present, data is provided/requested in a container format specified in the attribute of the flag + + + + + Name of the message, e.g. pain.001 or mt101 National message names (issued by DK, CFONB or SIC are also allowed) + + + + + + + + + Container flag. If present, data is provided/requested in a container format specified in the attribute of the flag + + + + + + Specifies the container type - External Codelist defined by EBICS (starting values: XML, ZIP, SVC) + + + + + + + + Datentyp für BTF Signatur-Flag (ersetzt Orderkennzeichen) + + + + + + If present the sender desires spooling into EBICS distributed signature queue, only "true" is allowed + + + + + + + + Datentyp zur Kennzeichnung von Auftragsartenbeschränkungen + + + + + Service Parameter-Sets von nicht unterstützten BTF-Auftragsarten + + + + + + diff --git a/spec/xsd/ebics_request_H005.xsd b/spec/xsd/ebics_request_H005.xsd new file mode 100644 index 00000000..dc7ad70a --- /dev/null +++ b/spec/xsd/ebics_request_H005.xsd @@ -0,0 +1,349 @@ + + + + + ebics_request_H005.xsd ist das EBICS-Protokollschema für Anfragen. + ebics_request_H005.xsd is the appropriate EBICS protocol schema for standard requests. + + + + + + + Electronic Banking Internet Communication Standard of the EBICS SCRL: Multibankfähige Schnittstelle zur internetbasierten Kommunikation. + Electronic Banking Internet Communication Standard der EBICS SCRL: multi-bank capable interface for internet-based communication. + + + + + + enthält die technischen Transaktionsdaten. + contains the transaction-driven data. + + + + + + enhält alle festen Headereinträge. + contains the static header entries. + + + + + enthält alle variablen Headereinträge. + contains the mutable header entries. + + + + + + + + + + enthält die Auftragsdaten, EU(s) und weitere Nutzdaten. + contains order data, order signature(s) and further data referring to the current order. + + + + + + + + + X.509-Daten des Teilnehmers. + X.509 data of the user. + + + + + Welche Transaktionsphase? + Which transaction phase? + + + + Initialisierungs- und Transferphase. + Initialisation or transfer phase. + + + + Daten zur Vorabprüfung; nur anzugeben in der Initialisierungsphase bei Uploads mit Auftragsattribut OZH (EUs + Auftragsdaten). + Data sent for pre-validation; mandatory for initialisation phase during uploads using order attribute OZH (order signature(s) + order data). + + + + + + + + + + + + Transfer von Signatur- bzw. Auftragsdaten; nur bei Upload anzugeben. + Transfer of signature or order data; mandatory for uploads only. + + + + + + Quittierungsphase nach Download. + Receipt phase after download. + + + + Quittierung des Transfers. + Receipt of transfer. + + + + + + + + + + + + + + + + + + + + + + Datentyp für den statischen EBICS-Header. + Data type for the static EBICS header. + + + + + Hostname des Banksystems. + + + + + Transaktionsphase? + Transaction phase? + + + + Initialisierungsphase. + Initialisation phase. + + + + Zufallswert; damit wird die Initialisierungsnachricht des Clients einzigartig. + Random value, ensures the uniqueness of the client's message during initialisation phase. + + + + + aktueller Zeitstempel zur Begrenzung der serverseitigen Nonce-Speicherung. + current timestamp, used to limit storage space for nonces on the server. + + + + + Kunden-ID des serverseitig administrierten Kunden. + ID of the partner = customer, administered on the server. + + + + + Teilnehmer-ID des serverseitig zu diesem Kunden administrierten Teilnehmers. + ID of the user that is assigned to the given customer, administered on the server. + + + + + technische User-ID für Multi-User-Systeme. + ID of the system for multi-user systems. + + + + + Kennung des Kundenprodukts bzw. Herstellerkennung oder Name. + software ID / manufacturer ID / manufacturer's name of the customer's software package. + + + + + + + Sprachkennzeichen der Kundenproduktversion (gemäß ISO 639). + Language code of the customer's software package according to ISO 639. + + + + + Kennung des Herausgebers des Kundenprodukts bzw. des betreuenden Kreditinstituts. + ID of the manufacturer / financial institute providing support for the customer's software package. + + + + + + + + + Auftragsdetails. + order details. + + + + + Hashwerte der erwarteten öffentlichen Schlüssel (Verschlüsselung, Signatur, Authentifikation) des Kreditinstituts. + Digest values of the expected public keys (authentication, encryption, signature) owned by the financial institute. + + + + + + Hashwert des Authentifikationsschlüssels. + Digest value of the public authentication key. + + + + + + + Version des Authentifikationsverfahrens. + Version of the algorithm used for authentication. + + + + + + + + + Hashwert des Verschlüsselungsschlüssels. + Digest value of the public encryption key. + + + + + + + Version des Verschlüsselungsverfahrens. + Version of the algorithm used for encryption. + + + + + + + + + Hashwert des Signaturschlüssels. + Digest value of the public signature key. + + + + + + + Version des Signaturverfahrens. + Version of the algorithm used for signature creation. + + + + + + + + + + + + Angabe des Sicherheitsmediums, das der Kunde verwendet. + Classification of the security medium used by the customer. + + + + + Gesamtsegmentanzahl für diese Transaktion; nur bei Uploads anzugeben. + Total number of segments for this transaction; mandatory for uploads only. + + + + + + + Transfer- und Quittierungsphase. + Transfer or receipt phase. + + + + eindeutige, technische Transaktions-ID; wird vom Server vergeben. + unique transaction ID, provided by the server. + + + + + + + + + Datentyp für den variablen EBICS-Header. + Data type for the mutable EBICS header. + + + + + Phase, in der sich die Transaktion gerade befindet; wird bei jedem Transaktionsschritt vom Client gesetzt und vom Server übernommen. + Current phase of the transaction; this information is provided by the client for each step of the transaction, and the server adopts the setting. + + + + + enthält die Nummer des aktuellen Segments, welches gerade übertragen oder angefordert wird; nur anzugeben bei TransactionPhase=Transfer. + contains the number of the segment which is currently being transmitted or requested; mandatory for transaction phase 'Transfer' only. + + + + + + + Ist dies das letzte Segment der Übertragung? + Is this segment meant to be the last one regarding this transmission? + + + + + + + + + + + + //MODIFIED - Removed OrderAtribute ELEMENT// Datentyp für Auftragsdetails im statischen EBICS-Header. + Data type for order details stored in the static EBICS header. + + + + + //MODIFIED - Umbenannt von OrderType// Auftragsart. + type code of the order. + + + + + + + + + + Auftragsnummer für Sendeaufträge gemäß DFÜ-Abkommen. + ID of the (upload) order, formatted in accordance with the document "DFÜ-Abkommen". + + + + + + diff --git a/spec/xsd/ebics_response_H005.xsd b/spec/xsd/ebics_response_H005.xsd new file mode 100644 index 00000000..841286b7 --- /dev/null +++ b/spec/xsd/ebics_response_H005.xsd @@ -0,0 +1,167 @@ + + + + + ebics_response_H005.xsd ist das EBICS-Protokollschema für Antwortnachrichten. + ebics_response_H005.xsd is the appropriate EBICS protocol schema for standard responses. + + + + XML-Signature. + + + + + + + Electronic Banking Internet Communication Standard des Zentralen Kreditausschusses (ZKA): Multibankfähige Schnittstelle zur internetbasierten Kommunikation. + Electronic Banking Internet Communication Standard of the "Zentraler Kreditausschuss (ZKA)": multi-bank capable interface for internet-based communication. + + + + + + enthält die technischen Transaktionsdaten. + contains the transaction-driven data. + + + + + + enhält alle festen Headereinträge. + contains the static header entries. + + + + + enthält alle variablen Headereinträge. + contains the mutable header entries. + + + + + + + + + Authentifikationssignatur. + Authentication signature. + + + + + enthält die Auftragsdaten, EU(s) und weitere Nutzdaten. + contains order data, order signature(s) and further data referring to the current order. + + + + + + Transfer von Auftragsdaten; nur bei Download anzugeben. + Transfer of signature or order data; mandatory for downloads only. + + + + + fachlicher Antwortcode für den vorangegangenen Request. + order-related return code of the previous request. + + + + + + + + + + + + Zeitstempel der letzten Aktualisierung der Bankparameter; nur in der Initialisierungsphase anzugeben. + timestamp indicating the latest update of the bank parameters; may be set during initialisation phase only. + + + + + + + + + + + + + + + + + + + + //TODO - Modify anotation TransactionID// Datentyp für den statischen EBICS-Header. + Data type for the static EBICS header. + + + + + eindeutige, technische Transaktions-ID; wird vom Server vergeben, falls OrderAttribute entweder gleich "OZHNN" oder gleich "DZHNN" ist und falls tatsächlich eine Transaktion erzeugt wurde. + unique transaction ID, provided by the server if and only if the order attribute is set to either "OZHNN" or "DZHNN" and if a transaction has been established actually. + + + + + Gesamtsegmentanzahl für diese Transaktion; nur bei Downloads in der Initialisierungsphase anzugeben. + Total number of segments for this transaction; mandatory for downloads in initialisation phase only. + + + + + + + Datentyp für den variablen EBICS-Header. + Data type for the mutable EBICS header. + + + + + Phase, in der sich die Transaktion gerade befindet; wird bei jedem Transaktionsschritt vom Client gesetzt und vom Server übernommen. + Current phase of the transaction; this information is provided by the client for each step of the transaction, and the server adopts the setting. + + + + + enthält die Nummer des aktuellen Segments, welches gerade übertragen oder angefordert wird; nur anzugeben bei TransactionPhase=Transfer und (bei Download) TransactionPhase=Initialisation. + contains the number of the segment which is currently being transmitted or requested; mandatory for transaction phases 'Transfer' and (for downloads) 'Initialisation' only. + + + + + + + Ist dies das letzte Segment der Übertragung? + + + + + + + + + Auftragsnummer von Sendeaufträgen gemäß DFÜ-Abkommen. + + + + + Rückmeldung des technischen Status mit einer eindeutigen Fehlernummer. + Return code indicating the technical status. + + + + + Klartext der Rückmeldung des technischen Status. + Textual interpretation of the returned technical status code. + + + + + + diff --git a/spec/xsd/ebics_signature_S002.xsd b/spec/xsd/ebics_signature_S002.xsd new file mode 100644 index 00000000..3127faa7 --- /dev/null +++ b/spec/xsd/ebics_signature_S002.xsd @@ -0,0 +1,177 @@ + + + + + + ebics_signature enthält Typdefinitionen für elektronische Unterschriften der Versionen A005, A006 und folgende. + ebics_EU contains type definitions for electronic signatures: versions A005, A006 and et sqq. + + + + + XML-Strukturen für bankfachliche Elektronische Unterschriften (EUs). + contains the digital signatures. + + + + + enthält die EUs der Teilnehmer. + contains the digital signatures. + + + + + Datentyp für Signaturdaten des Teilnehmers beim EU-Transfer. + Data type for digital signature data transferred using EBICS. + + + + + bankfachliche Elektronische Unterschrift oder Transportunterschrift (strukturiertes Format). + Digital signature (either autorising an order or applied for transportation), structured format. + + + + + + + + Datentyp für kryptographische Unterschriften. + + + + + + bankfachliche Elektronische Unterschrift oder Transportunterschrift (strukturiertes Format). + Digital signature (either autorising an order or applied for transportation), structured format. + + + + + Datentyp für bankfachliche Elektronische Unterschrift oder Transportunterschrift (strukturiertes Format). + Data type according for a digital signature (either autorising an order or applied for transportation), structured format. + + + + + Version des Signaturverfahrens. + Version of the algorithm used for signature creation. + + + + + Digitale Signatur. + Digital signature. + + + + + Kunden-ID des Unterzeichners. + Customer ID of the signer. + + + + + Teilnehmer-ID. + User ID. + + + + + Parameter zur X.509-Funktionalität + Parameter for X509Data + + + + + + + Datentyp für eine Kunden-ID. + + + + + + + + Datentyp für eine Teilnehmer-ID. + + + + + + + + Datentyp für Versionsnummern zur Elektronischen Unterschrift (EU). + + + + + + + + + + Element für Public Key Dateien unabhängig von der Auftragsart / Geschäftsvorfall. + + + + + Datentyp für Public Key Dateien unabhängig von der Auftragsart / Geschäftsvorfall. + + + + + öffentlicher Signaturschlüssel. + + + + + Kunden-ID. + + + + + Teilnehmer-ID. + + + + + + + + öffentlicher Signaturschlüssel. + + + + + Datentyp für öffentliche bankfachliche Schlüssel. + + + + + + + Version des EU-Signaturverfahrens. + + + + + + + + + Datentyp für die Darstellung eines öffentlichen RSA-Schlüssels als Exponent-Modulus-Kombination oder als X509-Zertifikat. + + + + + + + + + Datentyp für Zeitstempel. + + + + diff --git a/spec/xsd/ebics_types_H005.xsd b/spec/xsd/ebics_types_H005.xsd new file mode 100644 index 00000000..ea13f06b --- /dev/null +++ b/spec/xsd/ebics_types_H005.xsd @@ -0,0 +1,1885 @@ + + + + + + + ebics_types_H005.xsd enthält einfache Typdefinitionen für EBICS. + + + + Datentyp für EBICS-Versionsnummern. + + + + + + + + + Datentyp für EBICS-Revisionsnummern. + + + + + + + + Datentyp für Versionsnummern zur Verschlüsselung. + + + + + + + + Datentyp für Versionsnummern zur Elektronischen Unterschrift (EU). + + + + + + + + + Datentyp für Versionsnummern zur Authentifikation. + + + + + + + + + Datentyp für Versionsnummern zur Verschlüsselung, Signatur und Authentifkation. + + + + + + Datentyp für Währungen (Grundtyp). + + + + + + + + + dreistelliger Währungscode gemäß ISO 4217. + + + + + + Afghanistan: Afghani + + + + + Albanien: Lek + + + + + Armenien: Dram + + + + + Niederländische Antillen: Gulden + + + + + Angola: Kwanza + + + + + Argentinien: Peso + + + + + Australien: Dollar + + + + + Aruba: Florin + + + + + Aserbaidschan: Manat + + + + + Bosnien und Herzegowina: Konvertible Mark + + + + + Barbados: Dollar + + + + + Bangladesch: Taka + + + + + Bulgarien: Lew + + + + + Bahrain: Dinar + + + + + Bermuda: Dollar + + + + + Brunei: Dollar + + + + + Bolivien: Boliviano + + + + + Brasilien: Real + + + + + Bahamas: Dollar + + + + + Bhutan: Ngultrum + + + + + Botswana: Pula + + + + + Weißrussland (Belarus): Rubel + + + + + Belize: Dollar + + + + + Kanada: Dollar + + + + + Demokratische Republik Kongo: Franc + + + + + Schweiz: Franken + + + + + Chile: Peso + + + + + China (Volksrepublik): Renminbi Yuan + + + + + Kolumbien: Peso + + + + + Costa Rica: Colón + + + + + Serbien: Dinar + + + + + Kuba: Peso + + + + + Kap Verde: Escudo + + + + + Zypern (griechischer Teil): Pfund + + + + + Tschechien: Krone + + + + + Dschibuti: Franc + + + + + Dänemark: Krone + + + + + Dominikanische Republik: Peso + + + + + Algerien: Dinar + + + + + Ecuador (bis 2000): Sucre + + + + + Estland: Krone + + + + + Ägypten: Pfund + + + + + Äthiopien: Birr + + + + + Europäische Währungsunion: Euro + + + + + Fidschi: Dollar + + + + + Falklandinseln: Pfund + + + + + Vereinigtes Königreich: Pfund + + + + + Georgien: Lari + + + + + Ghana: Cedi + + + + + Gibraltar: Pfund + + + + + Gambia: Dalasi + + + + + Guinea: Franc + + + + + Guatemala: Quetzal + + + + + Guyana: Dollar + + + + + Hongkong: Dollar + + + + + Honduras: Lempira + + + + + Kroatien: Kuna + + + + + Haiti: Gourde + + + + + Ungarn: Forint + + + + + Indonesien: Rupiah + + + + + Israel: Schekel + + + + + Indien: Rupie + + + + + Irak: Dinar + + + + + Iran: Rial + + + + + Island: Krone + + + + + Jamaika: Dollar + + + + + Jordanien: Dinar + + + + + Japan: Yen + + + + + Kenia: Schilling + + + + + Kirgisistan: Som + + + + + Kambodscha: Riel + + + + + Komoren: Franc + + + + + Nordkorea: Won + + + + + Südkorea: Won + + + + + Kuwait: Dinar + + + + + Kaimaninseln: Dollar + + + + + Kasachstan: Tenge + + + + + Laos: Kip + + + + + Libanon: Pfund + + + + + Sri Lanka: Rupie + + + + + Liberia: Dollar + + + + + Lesotho: Loti + + + + + Litauen: Litas + + + + + Lettland: Lats + + + + + Libyen: Dinar + + + + + Marokko: Dirham + + + + + Moldawien: Leu + + + + + Madagaskar: Franc + + + + + Mazedonien: Denar + + + + + Myanmar: Kyat + + + + + Mongolei: Tugrik + + + + + Macau: Pataca + + + + + Mauretanien: Ouguiya + + + + + Malta: Lira + + + + + Mauritius: Rupie + + + + + Malediven: Rufiyaa + + + + + Malawi: Kwacha + + + + + Mexiko: Peso + + + + + Malaysia: Ringgit + + + + + Mosambik: Metical + + + + + Namibia: Dollar + + + + + Nigeria: Naira + + + + + Nicaragua: Cordoba Oro + + + + + Norwegen: Krone + + + + + Nepal: Rupie + + + + + Neuseeland: Dollar + + + + + Oman: Rial + + + + + Panama: Balboa + + + + + Peru: Nuevo Sol + + + + + Papua-Neuguinea: Kina + + + + + Philippinen: Peso + + + + + Pakistan: Rupie + + + + + Polen: Zloty + + + + + Paraguay: Guaraní + + + + + Katar: Riyal + + + + + Rumänien: Leu + + + + + Russland: Rubel + + + + + Ruanda: Franc + + + + + Saudi-Arabien: Riyal + + + + + Salomonen: Dollar + + + + + Seychellen: Rupie + + + + + Sudan: Dinar + + + + + Schweden: Krone + + + + + Singapur: Dollar + + + + + St. Helena: Pfund + + + + + Slowenien: Tolar + + + + + Slowakei: Krone + + + + + Sierra Leone: Leone + + + + + Somalia: Schilling + + + + + Suriname: Dollar + + + + + São Tomé und Príncipe: Dobra + + + + + El Salvador: Colón + + + + + Syrien: Pfund + + + + + Swasiland: Lilangeni + + + + + Thailand: Baht + + + + + Tadschikistan: Somoni + + + + + Turkmenistan: Manat + + + + + Tunesien: Dinar + + + + + Tonga: Pa'anga + + + + + Türkei: Lira + + + + + Türkei: Neue Lira (ab 2005) + + + + + Trinidad und Tobago: Dollar + + + + + Taiwan: Dollar + + + + + Tansania: Schilling + + + + + Ukraine: Hrywnja + + + + + Uganda: Shilling + + + + + USA: Dollar + + + + + Uruguay: Peso + + + + + Usbekistan: Sum + + + + + Venezuela: Bolivar + + + + + Vietnam: Dong + + + + + Vanuatu: Vatu + + + + + Samoa: Tala + + + + + Zentralafrikanische Wirtschafts- und Währungsunion: CFA-Franc + + + + + Ostkaribische Währungsunion: Dollar + + + + + Westafrikanische Wirtschafts- und Währungsunion: CFA-Franc + + + + + Neukaledonien: CFP-Franc + + + + + Spezialcode für Testzwecke; keine existierende Währung + + + + + keine Währung + + + + + Jemen: Rial + + + + + Südafrika: Rand + + + + + Sambia: Kwacha + + + + + Simbabwe: Dollar + + + + + + + Datentyp für einen Betragswert (ohne Währung). + + + + + + + + + Datentyp für einen Betrag inkl. Währungscode-Attribut (Default = "EUR"). + + + + + + Währungscode, Default="EUR". + Currency code, default setting is "EUR". + + + + + + + + Datentyp für die Transaktions-ID. + + + + + + + + Datentyp für Nonces. + + + + + + + + Datentyp für die Instituts-ID. + + + + + + + + Datentyp für die Host-ID. + + + + + + + + Datentyp für die Kundenprodukt-ID. + + + + + + + + Datentyp für das Sprachkennzeichen des Kundenprodukts. + + + + + + + + Datentyp für allgemeine Auftragsarten (Grundtyp). + + + + + + + + + Datentyp für eine Auftragsnummer lt. DFÜ-Abkommen. + + + + + + + + + Datentyp für das Sicherheitsmedium. + + + + + + + + + Datentyp für die Segmentnummer. + + + + + + + + Datentyp für die Gesamtsegmentanzahl. + + + + + + + + Datentyp für die Gesamtanzahl der Einzelauftraginfos. + + + + + + + + Datentyp für die Transaktionsphase. + + + + + Transaktionsinitialisierung + + + + + Auftragsdatentransfer + + + + + Quittungstransfer + + + + + + + Datentyp für Zeitstempel. + + + + + + Datentyp für Datumswerte. + + + + + + Datentyp für eine Teilnehmer-ID. + + + + + + + + + Datentyp für eine Kunden-ID. + + + + + + + + + Datentyp für eine Konten-ID. + + + + + + + + Datentyp für eine Kontonummer (national/international). + + + + + + + + + Datentyp für eine Bankleitzahl (national/international). + + + + + + + + + Datentyp für ein nationales BLZ-Präfix. + + + + + + + + Datentyp für eine Kontonummer (freies Format). + + + + + + + + Datentyp für eine Bankleitzahl (freies Format). + + + + + + + + Datentyp für den Namen des Kontoinhabers. + + + + + + Datentyp für die Kontobeschreibung. + + + + + + Datentyp für Kontoinformationen. + + + + + + Kontonummer (deutsches Format und/oder international als IBAN). + Account number (German format and/or international=IBAN). + + + + + + + Ist die Kontonummer im deutschen Format (international=false) oder im internationalen Format (international=true, IBAN) angegeben? + Is the account number specified using the national=German or the international=IBAN format? + + + + + + + + + Kontonummer im freien Format. + Account in free format. + + + + + + + Formatkennung. + Format identification. + + + + + + + + + + + Bankleitzahl (deutsches Format und/oder international als SWIFT-BIC). + Bank code (German and/or international=SWIFT-BIC). + + + + + + + Ist die Bankleitzahl im deutschen Format (international=false, BLZ) oder im internationalen Format (international=true, SWIFT-BIC) angegeben? + Is the bank code specified using the national=German or the international SWIFT-BIC format? + + + + + nationales Präfix für Bankleitzahlen. + National=German prefix for bank codes. + + + + + + + + + Bankleitzahl im freien Format. + Bank code in free format. + + + + + + + Formatkennung. + Format identification. + + + + + + + + + + Name des Kontoinhabers. + Name of the account holder. + + + + + + Währungscode für dieses Konto, Default=EUR. + Currency code for this account, Default=EUR. + + + + + Kontobeschreibung. + Description of this account. + + + + + + Datentyp für die Rolle eines Zahlungsverkehrskontos innerhalb einer Transaktion. + + + + + Auftraggeberkonto + + + + + Empfängerkonto + + + + + Gebührenkonto + + + + + andere Kontorolle + + + + + + + Datentyp für die Rolle eines Kreditinstituts innerhalb einer Transaktion (repräsentiert durch die Bankleitzahl). + + + + + Auftraggeberbank + + + + + Empfängerbank + + + + + Korrespondenzbank + + + + + andere Bankrolle + + + + + + + Datentyp für die Rolle eines Kontoinhabers innerhalb einer Transaktion. + + + + + Auftraggeber + + + + + Empfänger + + + + + Überbringer, Einreicher + + + + + andere Rolle + + + + + + + Datentyp für Kontoinformationen inkl. der Eigenschaftszuordnung innerhalb einer Zahlungstransaktion. + + + + + + Kontonummer (deutsches Format oder international als IBAN). + Kontonummer (Account number (German format and/or international = IBAN). + + + + + + + Rolle des Kontos innerhalb der Zahlungstransaktion. + Role of the account during the transaction. + + + + + Textuelle Beschreibung der Funktion, falls role=Other ausgewählt wird. + Textual description of the role the account place during the transaction; use only if the corresponding 'role' field is set to 'other'. + + + + + Ist die Kontonummer im deutschen Format (international=false) oder im internationalen Format (international=true, IBAN) angegeben? + Is the account number specified using the national=German or the international=IBAN format? + + + + + + + + + Kontonummer im freien Format. + Account in free format. + + + + + + + Rolle des Kontos innerhalb der Zahlungstransaktion. + Role of the account during the transaction. + + + + + Textuelle Beschreibung der Funktion, falls role=Other ausgewählt wird. + Textual description of the role the account place during the transaction; use only if the corresponding 'role' field is set to 'other'. + + + + + Formatkennung. + Format identification. + + + + + + + + + + + Bankleitzahl (deutsches Format oder international als SWIFT-BIC). + Bank code (German and/or international=SWIFT-BIC). + + + + + + + Rolle des kontoführenden Instituts innerhalb der Zahlungstransaktion. + Role of the bank during the transaction. + + + + + Textuelle Beschreibung der Funktion, falls role=Other ausgewählt wird. + Textual description of the role the account place during the transaction; use only if the corresponding 'role' field is set to 'other'. + + + + + Ist die Bankleitzahl im deutschen Format (international=false, BLZ) oder im internationalen Format (international=true, SWIFT-BIC) angegeben? + Is the bank code specified using the national=German or the international=SWIFT-BIC format? + + + + + nationales Präfix für Bankleitzahlen. + National=German prefix for bank codes. + + + + + + + + + Bankleitzahl im freien Format. + Bank code in free format. + + + + + + + Rolle des kontoführenden Instituts innerhalb der Zahlungstransaktion. + Role of the bank during the transaction. + + + + + Textuelle Beschreibung der Funktion, falls role=Other ausgewählt wird. + Textual description of the role the account place during the transaction; use only if the corresponding 'role' field is set to 'other'. + + + + + Formatkennung. + Format identification. + + + + + + + + + + Name des Kontoinhabers. + Name of the account holder. + + + + + + + Rolle des Kontoinhabers innerhalb der Zahlungstransaktion. + Role of the account holder during the transaction. + + + + + Textuelle Beschreibung der Rolle, falls role=Other ausgewählt wird. + Textual description of the role the account holder place during the transaction; use only if the corresponding 'role' field is set to 'other'. + + + + + + + + + + Währungscode für dieses Konto, Default=EUR. + Currency code for this account, Default=EUR. + + + + + Kontobeschreibung. + Description of this account. + + + + + + Datentyp für binäre Signaturdaten (komprimiert, verschlüsselt und kodiert). + + + + + + Datentyp für binäre Auftragsdaten (komprimiert, verschlüsselt und kodiert). + + + + + + Datentyp für Berechtigungsklassen zur Elektronischen Unterschrift. + + + + + + Einzelunterschrift + + + + + Erstunterschrift + + + + + Zweitunterschrift + + + + + Transportunterschrift + + + + + + + Listentyp für Berechtigungsklassen zur Elektronischen Unterschrift. + + + + + + Datentyp für Hashfunktionen. + + + + + + Datentyp für Hashwerte. + + + + + + + + + Version des Signaturverfahrens. + Version of the algorithm used for signature creation. + + + + + + + + Datentyp für kryptographische Unterschriften. + + + + + + Datentyp für symmetrische Schlüssel. + + + + + + Datentyp für Hashwerte und Attribute von öffentlichen Schlüsseln. + + + + + + Hashalgorithmus. + Name of the used hash algorithm. + + + + + + + + Datentyp für die Darstellung eines öffentlichen RSA-Schlüssels als Exponent-Modulus-Kombination oder als X509-Zertifikat. + + + + + + + + + Datentyp für öffentliche Verschlüsselungsschlüssel. + + + + + + + Version des Verschlüsselungsverfahrens. + + + + + + + + + Datentyp für öffentlichen Authentfikationsschlüssel. + + + + + + + Version des Authentifikationsverfahrens. + + + + + + + + + Datentyp für öffentliche bankfachliche Schlüssel. + Data type for public authorisation (ES) key. + + + + + + + Version des EU-Signaturverfahrens. + ES-Version. + + + + + + + + + Datentyp für öffentlichen Schlüssel zur Authentisierung. + Data type for public for identification and authentication. + + + + + + + Version des Authentifikationsverfahrens. + Authentication version. + + + + + + + + + Datentyp für öffentlichen Verschlüsselungsschlüssel. + Data type for encryption key. + + + + + + + Version des Verschlüsselungsverfahrens. + Encryption Version. + + + + + + + + + Datentyp für die Zertifikate hinsichtlich der "bank-technical signature for authorisation" (ES). + Data Type for Certificates for the bank-technical signature for authorisation (ES) + + + + + + + + + Datentyp für Antwortcodes. + + + + + + + + + Datentyp für den Erklärungstext zum Antwortcode. + + + + + + + + Datentyp für Quittierungscodes. + + + + + + + + + Datentyp für Kunden-, Teilnehmer-, Straßen- oder Ortsnamen. + + + + + + Datentyp für die Beschreibung von Auftragsarten. + + + + + + + + Datentyp für den Teilnehmerstatus. + + + + + + + + generic parameter + Generic key value parameters. + + + + + + name of parameter + Name of the parameter (= key). + + + + + value of parameter + Value of the parameter. + + + + + + + XML-Typ des Parameterwerts (Vorschlag für default ist string). + XML type of the parameter value (Proposal for default is string). + + + + + + + + + + + + Datentyp für die Darstellung von Information zur Verschlüsselung der Auftragsdaten. + Data type for the modelling of information regarding the encryption of signature and order data. + + + + + Hashwert des öffentlichen Verschlüsselungsschlüssels des Empfängers der verschlüsselten Auftragsdaten. + Hash value of the public encryption key owned by the receipient of the encrypted order data. + + + + + + + Version des Verschlüsselungsverfahrens. + Version of the encryption method. + + + + + + + + + Asymmetrisch verschlüsselter symmetrischer Transaktionsschlüssel. + The asymmetrically encrypted symmetric transaction key. + + + + + + + + Authentifikationssignatur. + Authentication signature. + + + + + String up to 255 characters. + + + + + + + + + + + + + + + + Type is used for ISO variant and version + + + + + + + + + + Type ist used for original file name + + + + + + + + + Type ist used for name or rather kind of Message + + + + + + + + + + Service Code name: External list specified and maintained by EBICS. Basis is the "SWIFT-list" for the field "description" (SCT, DCT, XCT, SDD, DDD, STM, REP...) plus additional codes needed for further services + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9a16ebebed3f45cf69a758f2ac3d8e47b1365511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Fri, 3 Jul 2026 13:21:21 +0200 Subject: [PATCH 02/12] Import bank keys from X.509 certificates in H005 HPB response The H005 HPB response (HPBResponseOrderDataType) transmits the bank's authentication and encryption keys only as X.509 certificates, with no PubKeyValue/RSAKeyValue. The existing parser found nothing on H005, so initialization could not complete. - HPB branches on version; H004 modulus/exponent path preserved (refactored into rsa_from_modulus_exponent) - hpb_h005 extracts the public key from each PubKeyInfo's X509Certificate, keyed by the version element (X002/E002), with an RSAKeyValue fallback - Spec: crafted H005 HPBResponseOrderData with self-signed bank certs proves bank_x/bank_e are imported correctly Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/epics/client.rb | 52 +++++++++++++++++++++++++++++++++------- spec/h005_client_spec.rb | 40 +++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/lib/epics/client.rb b/lib/epics/client.rb index 594263a3..f06a07ea 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -147,19 +147,16 @@ def HEV end def HPB - Nokogiri::XML(download(Epics::HPB)).xpath("//xmlns:PubKeyValue", xmlns: namespace).each do |node| + doc = Nokogiri::XML(download(Epics::HPB)) + return hpb_h005(doc) if h005? + + doc.xpath("//xmlns:PubKeyValue", xmlns: namespace).each do |node| type = node.parent.last_element_child.content modulus = Base64.decode64(node.at_xpath(".//*[local-name() = 'Modulus']").content) exponent = Base64.decode64(node.at_xpath(".//*[local-name() = 'Exponent']").content) - sequence = [] - sequence << OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(modulus, 2)) - sequence << OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(exponent, 2)) - - 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}"] = Epics::Key.new(rsa_from_modulus_exponent(modulus, exponent)) end [bank_x, bank_e] @@ -356,6 +353,45 @@ def x_509_certificate_hash(type) private + DSIG_NS = 'http://www.w3.org/2000/09/xmldsig#'.freeze + + # EBICS 3.0 (H005) HPB response: the bank's authentication and encryption keys + # are transmitted only as X.509 certificates (ds:X509Data). Extract the public + # key from each certificate rather than from a PubKeyValue/RSAKeyValue. + def hpb_h005(doc) + %w[AuthenticationPubKeyInfo EncryptionPubKeyInfo].each do |element| + info = doc.at_xpath("//xmlns:#{element}", xmlns: namespace) + next unless info + + # The version element (AuthenticationVersion / EncryptionVersion) is the + # last child and gives the key suffix (X002 / E002). + type = info.element_children.last.content + self.keys["#{host_id.upcase}.#{type}"] = Epics::Key.new(bank_key_from_info(info)) + end + + [bank_x, bank_e] + end + + def bank_key_from_info(info) + cert = info.at_xpath(".//ds:X509Certificate", ds: DSIG_NS) + if cert + OpenSSL::X509::Certificate.new(Base64.decode64(cert.content)).public_key + else + # Fallback: some banks additionally include a raw RSAKeyValue. + modulus = Base64.decode64(info.at_xpath(".//*[local-name() = 'Modulus']").content) + exponent = Base64.decode64(info.at_xpath(".//*[local-name() = 'Exponent']").content) + rsa_from_modulus_exponent(modulus, exponent) + end + end + + def rsa_from_modulus_exponent(modulus, exponent) + sequence = [ + OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(modulus, 2)), + OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(exponent, 2)), + ] + OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der) + end + KEY_FOR_CERT_TYPE = { a: 'A006', x: 'X002', e: 'E002' }.freeze def self_signed_certificate(type) diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index 4faf044b..0f6466a2 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -82,6 +82,46 @@ end end + describe 'HPB imports bank keys from X.509 certificates (H005)' do + let(:bank_auth) { OpenSSL::PKey::RSA.generate(2048) } + let(:bank_enc) { OpenSSL::PKey::RSA.generate(2048) } + + def cert_data(rsa) + Epics::X509Certificate.generate_self_signed(rsa, subject: '/CN=bank').data + end + + let(:hpb_response) do + <<~XML + + + + #{cert_data(bank_auth)} + X002 + + + #{cert_data(bank_enc)} + E002 + + SIZBN001 + + XML + end + + before { allow(client).to receive(:download).with(Epics::HPB).and_return(hpb_response) } + + it 'stores the bank authentication key (X002) from its certificate' do + client.HPB + expect(client.bank_x).to be_a(Epics::Key) + expect(client.bank_x.key.n).to eq(bank_auth.n) + end + + it 'stores the bank encryption key (E002) from its certificate' do + client.HPB + expect(client.bank_e).to be_a(Epics::Key) + expect(client.bank_e.key.n).to eq(bank_enc.n) + end + end + describe 'convenience methods route to BTU/BTD under H005' do it 'CCT builds a BTU with the SCT service' do order = Epics::BTU.new(client, '', service: Epics::BtfMapping.upload('CCT')) From 34ce0c862fa34a2542f18d37dfa6e50ab3a8086a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Fri, 3 Jul 2026 13:31:33 +0200 Subject: [PATCH 03/12] Parse H005 HTD/HAA BTF service discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit H005 replaced the flat OrderTypes list with BTF Service structures, so the old //OrderTypes parsing returned nothing on H005. - HAA (H005): parse //Service into Epics::BTF objects - HTD (H005): read OrderInfo entries — order_types now exposes the distinct AdminOrderType values, plus a new #services accessor for the BTF services; name/iban/bic XPaths carry over unchanged - service_from_node helper parses a element into an Epics::BTF - Specs: crafted H005 HAA/HTD response order data proves parsing Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/epics/client.rb | 44 +++++++++++++++++++- spec/h005_client_spec.rb | 88 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/lib/epics/client.rb b/lib/epics/client.rb index f06a07ea..f81fcdeb 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -93,6 +93,12 @@ def order_types @order_types ||= (self.HTD; @order_types) end + # EBICS 3.0 (H005): the available business transactions as Epics::BTF services + # (there is no flat OrderTypes list anymore). Empty on H004. + def services + @services ||= (self.HTD; @services) + end + def self.setup(passphrase, url, host_id, user_id, partner_id, keysize = 2048, options = {}) client = new(nil, passphrase, url, host_id, user_id, partner_id, options) client.keys = %w(A006 X002 E002).each_with_object({}) do |type, memo| @@ -296,7 +302,13 @@ def Z54(from, to) end def HAA - Nokogiri::XML(download(Epics::HAA)).at_xpath("//xmlns:OrderTypes", xmlns: namespace).content.split(/\s/) + doc = Nokogiri::XML(download(Epics::HAA)) + if h005? + # H005: available transactions are BTF services, not a flat OrderTypes list. + doc.xpath("//xmlns:Service", xmlns: namespace).map { |s| service_from_node(s) } + else + doc.at_xpath("//xmlns:OrderTypes", xmlns: namespace).content.split(/\s/) + end end def HTD @@ -304,7 +316,17 @@ def HTD @iban ||= htd.at_xpath("//xmlns:AccountNumber[@international='true']", xmlns: namespace).text rescue nil @bic ||= htd.at_xpath("//xmlns:BankCode[@international='true']", xmlns: namespace).text rescue nil @name ||= htd.at_xpath("//xmlns:Name", xmlns: namespace).text rescue nil - @order_types ||= htd.search("//xmlns:OrderTypes", xmlns: namespace).map{|o| o.content.split(/\s/) }.delete_if{|o| o == ""}.flatten + + if h005? + # H005 replaces OrderTypes with OrderInfo entries: each has an + # AdminOrderType and, for BTU/BTD, a BTF Service. + order_infos = htd.search("//xmlns:OrderInfo", xmlns: namespace) + @order_types ||= order_infos.map { |o| o.at_xpath("./xmlns:AdminOrderType", xmlns: namespace)&.content }.compact.uniq + @services ||= order_infos.map { |o| o.at_xpath("./xmlns:Service", xmlns: namespace) }.compact.map { |s| service_from_node(s) } + else + @order_types ||= htd.search("//xmlns:OrderTypes", xmlns: namespace).map{|o| o.content.split(/\s/) }.delete_if{|o| o == ""}.flatten + @services ||= [] + end end.to_xml end @@ -384,6 +406,24 @@ def bank_key_from_info(info) end end + # Parses a BTF element (from HAA/HTD responses) into an Epics::BTF. + def service_from_node(node) + msg = node.at_xpath("./xmlns:MsgName", xmlns: namespace) + container = node.at_xpath("./xmlns:Container", xmlns: namespace) + text = ->(name) { node.at_xpath("./xmlns:#{name}", xmlns: namespace)&.content } + + Epics::BTF.new( + service_name: text.call('ServiceName'), + scope: text.call('Scope'), + service_option: text.call('ServiceOption'), + container: container && (container['containerType'] || container.content), + msg_name: msg&.content, + msg_version: msg && msg['version'], + msg_variant: msg && msg['variant'], + msg_format: msg && msg['format'], + ) + end + def rsa_from_modulus_exponent(modulus, exponent) sequence = [ OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(modulus, 2)), diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index 0f6466a2..24f2b94d 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -122,6 +122,94 @@ def cert_data(rsa) end end + describe 'HAA lists available BTF services (H005)' do + let(:haa_response) do + <<~XML + + + + EOP + DE + + camt.053 + + + SCT + pain.001 + + + XML + end + + before { allow(client).to receive(:download).with(Epics::HAA).and_return(haa_response) } + + it 'returns parsed BTF services' do + services = client.HAA + expect(services.map(&:service_name)).to eq(%w[EOP SCT]) + expect(services.first.container).to eq('ZIP') + expect(services.first.msg_name).to eq('camt.053') + expect(services.first.msg_version).to eq('08') + end + end + + describe 'HTD parses account data and BTF services (H005)' do + let(:htd_response) do + <<~XML + + + + ACME Corp + SIZBN001 + + DE89370400440532013000 + COBADEFFXXX + + + BTD + + EOP + DE + + camt.053 + + Statements + + + BTU + + SCT + pain.001 + + Credit transfer + + + HAC + Acknowledgement + + + + + XML + end + + before { allow(client).to receive(:download).with(Epics::HTD).and_return(htd_response) } + + it 'parses name, iban and bic' do + expect(client.name).to eq('ACME Corp') + expect(client.iban).to eq('DE89370400440532013000') + expect(client.bic).to eq('COBADEFFXXX') + end + + it 'exposes distinct admin order types' do + expect(client.order_types).to eq(%w[BTD BTU HAC]) + end + + it 'exposes the BTF services' do + expect(client.services.map(&:service_name)).to eq(%w[EOP SCT]) + expect(client.services.first.msg_name).to eq('camt.053') + end + end + describe 'convenience methods route to BTU/BTD under H005' do it 'CCT builds a BTU with the SCT service' do order = Epics::BTU.new(client, '', service: Epics::BtfMapping.upload('CCT')) From 5f0ffaa26e5f5f7f61e7e4a01840236ae72bc190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Fri, 3 Jul 2026 13:48:52 +0200 Subject: [PATCH 04/12] Persist self-signed certificates in the key file Self-signed H005 certificates were memoized per client instance only, so every process restart generated a new certificate (fresh not_before). The certificate submitted via INI/HIA and the fingerprint printed on the INI letter could then diverge between runs, and the bank would reject the subscriber verification. Certificates now live in the encrypted keys blob under a ".crt" suffix ("A006.crt", ...): dump_keys serializes them, extract_keys restores them, and self_signed_certificate reuses the persisted one. Existing key files without .crt entries load unchanged. Co-Authored-By: Claude Fable 5 --- lib/epics/client.rb | 37 ++++++++++++++++++++++++++++------ lib/epics/x_509_certificate.rb | 2 +- spec/h005_client_spec.rb | 23 +++++++++++++++++++++ 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/epics/client.rb b/lib/epics/client.rb index f81fcdeb..4e9ac691 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -73,6 +73,16 @@ def bank_e keys["#{host_id.upcase}.E002"] end + CERTIFICATE_SUFFIX = '.crt'.freeze + + # X.509 certificates belonging to the user's keys, keyed like "A006.crt". + # Populated from the key file (entries suffixed .crt) and by generated + # self-signed certificates (H005); persisted alongside the keys via + # save_keys/dump_keys so certificates stay stable across processes. + def certificates + @certificates ||= {} + end + def bank_x keys["#{host_id.upcase}.X002"] end @@ -435,10 +445,14 @@ def rsa_from_modulus_exponent(modulus, exponent) KEY_FOR_CERT_TYPE = { a: 'A006', x: 'X002', e: 'E002' }.freeze def self_signed_certificate(type) - key = keys[KEY_FOR_CERT_TYPE.fetch(type.to_sym)] + key_name = KEY_FOR_CERT_TYPE.fetch(type.to_sym) + key = keys[key_name] return unless key - @self_signed_certificates ||= {} - @self_signed_certificates[type.to_sym] ||= + + # Stored in #certificates so save_keys persists it — the certificate (and + # thus its fingerprint on the INI letter) must stay identical across + # processes, otherwise the bank cannot verify the subscriber. + certificates["#{key_name}#{CERTIFICATE_SUFFIX}"] ||= Epics::X509Certificate.generate_self_signed( key.key, subject: "/CN=#{user_id}/O=#{partner_id}/OU=#{host_id}" @@ -501,13 +515,24 @@ def connection end def extract_keys - JSON.load(self.keys_content).each_with_object({}) do |(type, key), memo| - memo[type] = Epics::Key.new(decrypt(key)) if key + JSON.load(self.keys_content).each_with_object({}) do |(type, blob), memo| + next unless blob + + # Entries suffixed with .crt are persisted X.509 certificates (H005 + # self-signed certs survive process restarts this way); everything else + # is an RSA key. + if type.end_with?(CERTIFICATE_SUFFIX) + certificates[type] = Epics::X509Certificate.new(decrypt(blob)) + else + memo[type] = Epics::Key.new(decrypt(blob)) + end end end def dump_keys - JSON.dump(keys.each_with_object({}) {|(k,v),m| m[k]= encrypt(v.key.to_pem)}) + data = keys.each_with_object({}) { |(k, v), m| m[k] = encrypt(v.key.to_pem) } + certificates.each { |k, v| data[k] = encrypt(v.to_pem) } + JSON.dump(data) end def new_cipher diff --git a/lib/epics/x_509_certificate.rb b/lib/epics/x_509_certificate.rb index 1143d617..8119b8d1 100644 --- a/lib/epics/x_509_certificate.rb +++ b/lib/epics/x_509_certificate.rb @@ -3,7 +3,7 @@ class Epics::X509Certificate attr_reader :certificate - def_delegators :certificate, :issuer, :version, :serial + def_delegators :certificate, :issuer, :version, :serial, :to_pem def initialize(crt_content) @certificate = diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index 24f2b94d..61bd01cf 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -70,6 +70,29 @@ expect(sig).not_to include('RSAKeyValue') end + it 'persists self-signed certificates across dump_keys/extract_keys round trips' do + fingerprint = client.x_509_certificate(:a).fingerprint + + reloaded = Epics::Client.new( + client.send(:dump_keys), 'secret', 'https://example.com', 'SIZBN001', 'EBIX', 'EBICS', + version: :h005 + ) + + expect(reloaded.x_509_certificate(:a).fingerprint).to eq(fingerprint) + end + + it 'does not leak certificate entries into the RSA key set' do + client.x_509_certificate(:a) + + reloaded = Epics::Client.new( + client.send(:dump_keys), 'secret', 'https://example.com', 'SIZBN001', 'EBIX', 'EBICS', + version: :h005 + ) + + expect(reloaded.keys.keys).not_to include(a_string_ending_with('.crt')) + expect(reloaded.certificates.keys).to include('A006.crt') + end + context 'S002 XSD validity', if: ebics_xsd_available?(:h005) do let(:s002) do Nokogiri::XML::Schema(File.open(File.join(File.dirname(__FILE__), 'xsd', 'ebics_signature_S002.xsd'))) From 0de3de1f83329102674fa74496781c19e8be1bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Fri, 3 Jul 2026 13:50:52 +0200 Subject: [PATCH 05/12] Read HPB key type from the explicit version element hpb_h005 derived the key suffix (X002/E002) from the PubKeyInfo's last child element. The schema allows arbitrary foreign-namespace elements after AuthenticationVersion/EncryptionVersion, so a bank appending one would have made the parser read the wrong content as the key type. Look up AuthenticationVersion/EncryptionVersion by name instead; the HPB spec fixture now carries a trailing wildcard element to pin this down. Co-Authored-By: Claude Fable 5 --- lib/epics/client.rb | 13 +++++++++---- spec/h005_client_spec.rb | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/epics/client.rb b/lib/epics/client.rb index 4e9ac691..628ac218 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -387,17 +387,22 @@ def x_509_certificate_hash(type) DSIG_NS = 'http://www.w3.org/2000/09/xmldsig#'.freeze + HPB_KEY_INFOS = { + 'AuthenticationPubKeyInfo' => 'AuthenticationVersion', + 'EncryptionPubKeyInfo' => 'EncryptionVersion', + }.freeze + # EBICS 3.0 (H005) HPB response: the bank's authentication and encryption keys # are transmitted only as X.509 certificates (ds:X509Data). Extract the public # key from each certificate rather than from a PubKeyValue/RSAKeyValue. def hpb_h005(doc) - %w[AuthenticationPubKeyInfo EncryptionPubKeyInfo].each do |element| + HPB_KEY_INFOS.each do |element, version_element| info = doc.at_xpath("//xmlns:#{element}", xmlns: namespace) next unless info - # The version element (AuthenticationVersion / EncryptionVersion) is the - # last child and gives the key suffix (X002 / E002). - type = info.element_children.last.content + type = info.at_xpath("./xmlns:#{version_element}", xmlns: namespace)&.content + next unless type + self.keys["#{host_id.upcase}.#{type}"] = Epics::Key.new(bank_key_from_info(info)) end diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index 61bd01cf..d186bca4 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -120,6 +120,7 @@ def cert_data(rsa) #{cert_data(bank_auth)} X002 + allowed by the schema's any-wildcard #{cert_data(bank_enc)} From f986b55045f185c6fee7192b9ee6a34461fb9881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Fri, 3 Jul 2026 13:52:42 +0200 Subject: [PATCH 06/12] Raise a descriptive error when HPB provides no usable bank key If a PubKeyInfo contained neither an X509Certificate nor an RSAKeyValue, bank_key_from_info died with a bare NoMethodError on nil. Raise a message naming the offending element instead. Co-Authored-By: Claude Fable 5 --- lib/epics/client.rb | 16 +++++++++------- spec/h005_client_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/epics/client.rb b/lib/epics/client.rb index 628ac218..1b61b800 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -411,14 +411,16 @@ def hpb_h005(doc) def bank_key_from_info(info) cert = info.at_xpath(".//ds:X509Certificate", ds: DSIG_NS) - if cert - OpenSSL::X509::Certificate.new(Base64.decode64(cert.content)).public_key - else - # Fallback: some banks additionally include a raw RSAKeyValue. - modulus = Base64.decode64(info.at_xpath(".//*[local-name() = 'Modulus']").content) - exponent = Base64.decode64(info.at_xpath(".//*[local-name() = 'Exponent']").content) - rsa_from_modulus_exponent(modulus, exponent) + return OpenSSL::X509::Certificate.new(Base64.decode64(cert.content)).public_key if cert + + # Fallback: some banks additionally include a raw RSAKeyValue. + modulus = info.at_xpath(".//*[local-name() = 'Modulus']") + exponent = info.at_xpath(".//*[local-name() = 'Exponent']") + unless modulus && exponent + raise "HPB response: #{info.name} contains neither an X509Certificate nor an RSAKeyValue — cannot import the bank key" end + + rsa_from_modulus_exponent(Base64.decode64(modulus.content), Base64.decode64(exponent.content)) end # Parses a BTF element (from HAA/HTD responses) into an Epics::BTF. diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index d186bca4..3b07d391 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -144,6 +144,27 @@ def cert_data(rsa) expect(client.bank_e).to be_a(Epics::Key) expect(client.bank_e.key.n).to eq(bank_enc.n) end + + context 'when a PubKeyInfo carries neither certificate nor RSAKeyValue' do + let(:hpb_response) do + <<~XML + + + + X002 + + + E002 + + SIZBN001 + + XML + end + + it 'raises a descriptive error' do + expect { client.HPB }.to raise_error(/neither an X509Certificate nor an RSAKeyValue/) + end + end end describe 'HAA lists available BTF services (H005)' do From 5c594b4962fb20b7664659a5ab0c3f60ce08ecc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Fri, 3 Jul 2026 13:57:24 +0200 Subject: [PATCH 07/12] Render INI letter fingerprints for self-signed H005 certificates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit x_509_certificate_hash only read externally supplied certificate content, so an H005 client with generated self-signed certificates rendered the plain key letter — without the certificate fingerprints the bank needs for subscriber verification. Route the hash through x_509_certificate (which covers self-signed certs) and let the template print the PEM via the parsed certificate instead of the raw content option. Externally supplied certificates render as before. Co-Authored-By: Claude Fable 5 --- lib/epics/client.rb | 7 +++---- lib/epics/letter_renderer.rb | 16 ++++++++++++++-- lib/letter/ini_with_certs.erb | 6 +++--- spec/h005_client_spec.rb | 8 ++++++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/epics/client.rb b/lib/epics/client.rb index 1b61b800..ab0f6876 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -377,10 +377,9 @@ def x_509_certificate(type) end def x_509_certificate_hash(type) - content = x_509_certificates_content[type.to_sym] - return if content.nil? || content.empty? - cert = OpenSSL::X509::Certificate.new(content) - Digest::SHA256.hexdigest(cert.to_der).upcase + # Routes through x_509_certificate so H005 self-signed certificates get a + # fingerprint too — the INI letter needs it for subscriber verification. + x_509_certificate(type)&.fingerprint end private diff --git a/lib/epics/letter_renderer.rb b/lib/epics/letter_renderer.rb index 7bd788e0..480ebfa7 100644 --- a/lib/epics/letter_renderer.rb +++ b/lib/epics/letter_renderer.rb @@ -31,12 +31,24 @@ def use_x_509_certificate_template? def x_509_certificate_a_hash @client.x_509_certificate_hash(:a) end - + def x_509_certificate_x_hash @client.x_509_certificate_hash(:x) end - + def x_509_certificate_e_hash @client.x_509_certificate_hash(:e) end + + def x_509_certificate_a_pem + @client.x_509_certificate(:a)&.to_pem + end + + def x_509_certificate_x_pem + @client.x_509_certificate(:x)&.to_pem + end + + def x_509_certificate_e_pem + @client.x_509_certificate(:e)&.to_pem + end end diff --git a/lib/letter/ini_with_certs.erb b/lib/letter/ini_with_certs.erb index c5bdde16..ca180676 100644 --- a/lib/letter/ini_with_certs.erb +++ b/lib/letter/ini_with_certs.erb @@ -109,7 +109,7 @@

<%= t('certificate') %> :

-
<%= @client.x_509_certificates_content[:a] %>
+
<%= x_509_certificate_a_pem %>

<%= t('hash') %> (SHA-256) :

<%= x_509_certificate_a_hash.scan(/../).join(":") %>

<%= t('confirmation') %>

@@ -218,7 +218,7 @@

<%= t('certificate') %> :

-
<%= @client.x_509_certificates_content[:x] %>
+
<%= x_509_certificate_x_pem %>

<%= t('hash') %> (SHA-256) :

<%= x_509_certificate_x_hash.scan(/../).join(":") %>

@@ -298,7 +298,7 @@

<%= t('certificate') %> :

-
<%= @client.x_509_certificates_content[:e] %>
+
<%= x_509_certificate_e_pem %>

<%= t('hash') %> (SHA-256) :

<%= x_509_certificate_e_hash.scan(/../).join(":") %>

<%= t('confirmation') %>

diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index 3b07d391..3f02b41f 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -70,6 +70,14 @@ expect(sig).not_to include('RSAKeyValue') end + it 'renders the INI letter with the self-signed certificate fingerprints' do + letter = client.ini_letter('Testbank') + fingerprint = client.x_509_certificate_hash(:a).scan(/../).join(':') + + expect(letter).to include(fingerprint) + expect(letter).to include('BEGIN CERTIFICATE') + end + it 'persists self-signed certificates across dump_keys/extract_keys round trips' do fingerprint = client.x_509_certificate(:a).fingerprint From fc4184ee55e275fecf58733e951f0f454dfd650a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Fri, 3 Jul 2026 13:58:06 +0200 Subject: [PATCH 08/12] add claude.md --- CLAUDE.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..c7b7409e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,106 @@ +# CLAUDE.md + +Guidance for working in this repository. + +## What this is + +`epics` is a Ruby gem implementing the [EBICS](https://www.ebics.org/) protocol +(Electronic Banking Internet Communication Standard) — a bank-communication +standard used mainly in Germany/France/Switzerland. It handles the key +initialization handshake (INI / HIA / HPB), signs requests, and exchanges +payment and statement order types with a bank's EBICS server. + +- Pure library gem (no Rails). Entry point: `require "epics"`. +- Supports **EBICS 2.5 (H004, default)** and **EBICS 3.0 (H005, opt-in)**. +- License LGPL-3.0. Published to RubyGems as `epics`. + +## Commands + +```bash +bin/setup # bundle install +bundle exec rspec # run the full test suite +bundle exec rspec spec/client_spec.rb # single file +bundle exec rspec spec/client_spec.rb:42 # single example +bin/console # irb with the gem loaded (pry available) +``` + +- Ruby: developed on **3.3.7** (`.tool-versions`); CI matrix runs 3.2 / 3.3 / 3.4 / 4.0. +- `required_ruby_version >= 3.1`. +- CI is Semaphore (`.semaphore/semaphore.yml`) — just `bundle install` + `rspec`. +- No linter/formatter is configured; match surrounding style. + +## Architecture + +Everything is namespaced under the `Epics` module (`Ebics` is an alias). +`lib/epics.rb` is the manifest: it `require`s every file explicitly (no +autoloading) and defines protocol constants (`EBICS_PROTOCOLS`, `DEFAULT_VERSION`). + +### The three layers + +1. **`Epics::Client`** (`lib/epics/client.rb`) — the public API and the only + object users instantiate. Holds credentials + RSA keys, exposes one method + per order type (`STA`, `CCT`, `CDD`, `HPB`, `HTD`, …) plus convenience + wrappers (`credit`, `debit`, `statements`). Owns the Faraday `connection` + and key encryption/decryption (AES-256-CBC over a passphrase). + +2. **Order classes** (`lib/epics/.rb`, e.g. `sta.rb`, `cct.rb`) — one + small class per EBICS order type. Each subclasses `GenericRequest` + (downloads) or `GenericUploadRequest` (uploads) and typically only overrides + `#header` to declare `order_type`, `order_attribute`, and params. This is + the dominant pattern — to add an order type, copy the closest sibling. + +3. **Request builders + middleware** + - `generic_request.rb` / `generic_upload_request.rb` / `header_request.rb` — + build the EBICS XML envelope via Nokogiri. + - `middleware/xmlsig.rb` — Faraday middleware that signs the outgoing XML + (`Epics::Signer` + `signer.rb`) before it leaves. + - `middleware/parse_ebics.rb` — Faraday middleware that wraps every response + in `Epics::Response` and raises `Epics::Error::TechnicalError` / + `BusinessError` on non-OK return codes. + +### Transaction flow + +`Client#download` / `#upload` (near the bottom of `client.rb`) run the +multi-step EBICS handshake: an initialization POST returns a `transaction_id`, +followed by transfer/receipt POSTs. `download_and_unzip` additionally unpacks +the ZIP payload (used by camt orders C52/C53/C54, Z-types, BKA…). Response +parsing lives in `response.rb`; RSA/key handling in `key.rb`. + +### H004 vs H005 (important) + +The gem defaults to H004. H005 is selected via `Epics::Client.new(..., version: :h005)`. + +- `client.protocol` / `namespace` / `protocol_version` / `revision` / `h005?` + derive from the configured version. +- Under H005 there is **no flat OrderTypes list** — transactions are + **BTF services** (`btf.rb`, `btf_mapping.rb`, `Client#services`). Generic + transfer is `BTU` (upload, replaces FUL) / `BTD` (download, replaces FDL). +- Many classic order methods branch: e.g. `CCT`/`CDD`/`STA`/`C53` call + `btf_upload` / `btf_download` when `h005?`. When touching an order type, + check whether it needs an H005 branch. +- X.509 certificate support (`x_509_certificate.rb`, `letter/ini_with_certs.erb`) + is part of the H005 path. + +### INI letter + +`letter_renderer.rb` renders the paper initialization letter from +`lib/letter/*.erb`, localized via `lib/letter/locales/*.yml` (de/en/fr, i18n). + +## Tests + +- RSpec, config in `spec/spec_helper.rb` (`--require spec_helper` via `.rspec`). +- **WebMock** stubs all HTTP — tests never hit a real bank. +- Real EBICS responses live as fixtures in `spec/fixtures/xml/`; RSA keys/certs + in `spec/fixtures/*.pem` and `*.key`. Compare XML with `equivalent-xml`'s + `be_equivalent_to` matcher (namespace/whitespace-insensitive). +- H005-specific coverage is in `spec/h005_client_spec.rb`. +- Helpers/shared setup in `spec/support/`. + +## Conventions + +- One file per order type, named after the 3-letter EBICS code, lowercased + (`cct.rb` → `Epics::CCT`). Register new files in `lib/epics.rb`. +- Keep order classes minimal — push shared logic into the `Generic*` base + classes, not into individual order types. +- `# frozen_string_literal: true` is used on newer files; keep it when editing them. +- Bump `Epics::VERSION` (`lib/epics/version.rb`) and update `CHANGELOG.md` for releases. From f3753e918eb1b1e24e7c1083d9a7fb85e564647d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Tue, 7 Jul 2026 14:00:49 +0200 Subject: [PATCH 09/12] Allow per-call BTF overrides on H005 convenience methods The H005 convenience methods (CCT/CCS/CDD/CDB, STA/VMK/C52/C53/C54) relied on BtfMapping's hardcoded German starter-set defaults, so a bank expecting a different message version, scope, or service option forced callers down to the raw BTU/BTD API. Thread keyword overrides through the convenience methods into BtfMapping, merging them onto the mapped default (nil values ignored). Renamed the BTF version field msg_version -> msg_name_version to match the naming in railslove/epics PR #154, easing a future switch. Co-Authored-By: Claude Opus 4.8 --- lib/epics/btd.rb | 2 +- lib/epics/btf.rb | 10 ++++----- lib/epics/btf_mapping.rb | 32 +++++++++++++++------------ lib/epics/btu.rb | 2 +- lib/epics/client.rb | 48 +++++++++++++++++++++------------------- spec/btf_spec.rb | 16 +++++++++++++- spec/h005_client_spec.rb | 10 ++++++++- spec/orders/btd_spec.rb | 2 +- spec/orders/btu_spec.rb | 2 +- 9 files changed, 76 insertions(+), 48 deletions(-) diff --git a/lib/epics/btd.rb b/lib/epics/btd.rb index bdc2b54d..3703f7ae 100644 --- a/lib/epics/btd.rb +++ b/lib/epics/btd.rb @@ -3,7 +3,7 @@ # EBICS 3.0 (H005) generic download order. Replaces the H004 FDL order: instead # of a FileFormat string the transfer is described by a BTF . # -# client.BTD(Epics::BTF.new(service_name: "EOP", scope: "DE", msg_name: "camt.053", msg_version: "08"), from: "2026-01-01", to: "2026-01-31") +# client.BTD(Epics::BTF.new(service_name: "EOP", scope: "DE", msg_name: "camt.053", msg_name_version: "08"), from: "2026-01-01", to: "2026-01-31") class Epics::BTD < Epics::GenericRequest def header client.header_request.build( diff --git a/lib/epics/btf.rb b/lib/epics/btf.rb index f80d3303..e5d771df 100644 --- a/lib/epics/btf.rb +++ b/lib/epics/btf.rb @@ -19,22 +19,22 @@ # service_name: "SCT", # scope: "DE", # msg_name: "pain.001", -# msg_version: "03", +# msg_name_version: "03", # ) # # A plain Hash with the same keys is accepted anywhere a BTF is expected. class Epics::BTF attr_reader :service_name, :service_option, :scope, :container, - :msg_name, :msg_version, :msg_variant, :msg_format + :msg_name, :msg_name_version, :msg_variant, :msg_format def initialize(service_name:, msg_name:, scope: nil, service_option: nil, - container: nil, msg_version: nil, msg_variant: nil, msg_format: nil) + container: nil, msg_name_version: nil, msg_variant: nil, msg_format: nil) @service_name = service_name @service_option = service_option @scope = scope @container = container @msg_name = msg_name - @msg_version = msg_version + @msg_name_version = msg_name_version @msg_variant = msg_variant @msg_format = msg_format end @@ -48,7 +48,7 @@ def to_h container: container, msg_name: { name: msg_name, - version: msg_version, + version: msg_name_version, variant: msg_variant, format: msg_format, }, diff --git a/lib/epics/btf_mapping.rb b/lib/epics/btf_mapping.rb index 03779da6..1c45319f 100644 --- a/lib/epics/btf_mapping.rb +++ b/lib/epics/btf_mapping.rb @@ -13,38 +13,42 @@ module Epics::BtfMapping # code => [direction, btf-attributes] UPLOADS = { - 'CCT' => { service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_version: '03' }, - 'CCS' => { service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_version: '03' }, - 'CDD' => { service_name: 'SDD', service_option: 'COR', scope: 'DE', msg_name: 'pain.008', msg_version: '02' }, - 'CDB' => { service_name: 'SDD', service_option: 'B2B', scope: 'DE', msg_name: 'pain.008', msg_version: '02' }, + 'CCT' => { service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_name_version: '03' }, + 'CCS' => { service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_name_version: '03' }, + 'CDD' => { service_name: 'SDD', service_option: 'COR', scope: 'DE', msg_name: 'pain.008', msg_name_version: '02' }, + 'CDB' => { service_name: 'SDD', service_option: 'B2B', scope: 'DE', msg_name: 'pain.008', msg_name_version: '02' }, }.freeze DOWNLOADS = { 'STA' => { service_name: 'EOP', scope: 'DE', msg_name: 'mt940' }, - 'C53' => { service_name: 'EOP', scope: 'DE', container: 'ZIP', msg_name: 'camt.053', msg_version: '08' }, - 'C52' => { service_name: 'STM', scope: 'DE', container: 'ZIP', msg_name: 'camt.052', msg_version: '08' }, - 'C54' => { service_name: 'REP', scope: 'DE', container: 'ZIP', msg_name: 'camt.054', msg_version: '08' }, + 'C53' => { service_name: 'EOP', scope: 'DE', container: 'ZIP', msg_name: 'camt.053', msg_name_version: '08' }, + 'C52' => { service_name: 'STM', scope: 'DE', container: 'ZIP', msg_name: 'camt.052', msg_name_version: '08' }, + 'C54' => { service_name: 'REP', scope: 'DE', container: 'ZIP', msg_name: 'camt.054', msg_name_version: '08' }, 'VMK' => { service_name: 'STM', scope: 'DE', msg_name: 'mt942' }, - 'PSR' => { service_name: 'PSR', scope: 'DE', msg_name: 'pain.002', msg_version: '03' }, + 'PSR' => { service_name: 'PSR', scope: 'DE', msg_name: 'pain.002', msg_name_version: '03' }, }.freeze module_function - def upload(code) - lookup(UPLOADS, code) + # `overrides` lets callers replace individual BTF attributes (e.g. scope, + # msg_name_version, service_option) on top of the starter-set defaults, so a bank + # that expects a different message version or scope can be served without + # dropping to the raw BTU/BTD API. nil overrides are ignored. + def upload(code, **overrides) + lookup(UPLOADS, code, overrides) end - def download(code) - lookup(DOWNLOADS, code) + def download(code, **overrides) + lookup(DOWNLOADS, code, overrides) end - def lookup(table, code) + def lookup(table, code, overrides = {}) attrs = table[code.to_s] unless attrs raise ArgumentError, "No H005 BTF mapping for order code #{code.inspect}. Use the raw " \ "Epics::Client#BTU / #BTD API with an Epics::BTF instead." end - Epics::BTF.new(**attrs) + Epics::BTF.new(**attrs.merge(overrides.compact)) end end diff --git a/lib/epics/btu.rb b/lib/epics/btu.rb index ebcb2903..86610948 100644 --- a/lib/epics/btu.rb +++ b/lib/epics/btu.rb @@ -3,7 +3,7 @@ # EBICS 3.0 (H005) generic upload order. Replaces the H004 FUL order: instead of # a FileFormat string the transfer is described by a BTF . # -# client.BTU(document, Epics::BTF.new(service_name: "SCT", scope: "DE", msg_name: "pain.001", msg_version: "03")) +# client.BTU(document, Epics::BTF.new(service_name: "SCT", scope: "DE", msg_name: "pain.001", msg_name_version: "03")) class Epics::BTU < Epics::GenericUploadRequest def header client.header_request.build( diff --git a/lib/epics/client.rb b/lib/epics/client.rb index ab0f6876..1a31d384 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -186,8 +186,8 @@ def CD1(document) upload(Epics::CD1, document) end - def CDB(document) - return btf_upload('CDB', document) if h005? + def CDB(document, **overrides) + return btf_upload('CDB', document, **overrides) if h005? upload(Epics::CDB, document) end @@ -195,8 +195,8 @@ def C2S(document) upload(Epics::C2S, document) end - def CDD(document) - return btf_upload('CDD', document) if h005? + def CDD(document, **overrides) + return btf_upload('CDD', document, **overrides) if h005? upload(Epics::CDD, document) end @@ -216,8 +216,8 @@ def XDS(document) upload(Epics::XDS, document) end - def CCT(document) - return btf_upload('CCT', document) if h005? + def CCT(document, **overrides) + return btf_upload('CCT', document, **overrides) if h005? upload(Epics::CCT, document) end @@ -225,8 +225,8 @@ def CIP(document) upload(Epics::CIP, document) end - def CCS(document) - return btf_upload('CCS', document) if h005? + def CCS(document, **overrides) + return btf_upload('CCS', document, **overrides) if h005? upload(Epics::CCS, document) end @@ -250,8 +250,8 @@ def BTD(service, from: nil, to: nil, parameters: nil) download(Epics::BTD, service: service, from: from, to: to, parameters: parameters) end - def STA(from = nil, to = nil) - return btf_download('STA', from, to) if h005? + def STA(from = nil, to = nil, **overrides) + return btf_download('STA', from, to, **overrides) if h005? download(Epics::STA, from: from, to: to) end @@ -259,8 +259,8 @@ def FDL(format, from = nil, to = nil) download(Epics::FDL, file_format: format, from: from, to: to ) end - def VMK(from = nil, to = nil) - return btf_download('VMK', from, to) if h005? + def VMK(from = nil, to = nil, **overrides) + return btf_download('VMK', from, to, **overrides) if h005? download(Epics::VMK, from: from, to: to) end @@ -276,18 +276,18 @@ def BKA(from, to) download_and_unzip(Epics::BKA, from: from, to: to) end - def C52(from, to) - return btf_download('C52', from, to) if h005? + def C52(from, to, **overrides) + return btf_download('C52', from, to, **overrides) if h005? download_and_unzip(Epics::C52, from: from, to: to) end - def C53(from, to) - return btf_download('C53', from, to) if h005? + def C53(from, to, **overrides) + return btf_download('C53', from, to, **overrides) if h005? download_and_unzip(Epics::C53, from: from, to: to) end - def C54(from, to) - return btf_download('C54', from, to) if h005? + def C54(from, to, **overrides) + return btf_download('C54', from, to, **overrides) if h005? download_and_unzip(Epics::C54, from: from, to: to) end @@ -434,7 +434,7 @@ def service_from_node(node) service_option: text.call('ServiceOption'), container: container && (container['containerType'] || container.content), msg_name: msg&.content, - msg_version: msg && msg['version'], + msg_name_version: msg && msg['version'], msg_variant: msg && msg['variant'], msg_format: msg && msg['format'], ) @@ -466,12 +466,14 @@ def self_signed_certificate(type) end # Route a classic order code to its H005 BTF Service via Epics::BtfMapping. - def btf_upload(code, document) - self.BTU(document, Epics::BtfMapping.upload(code)) + # `overrides` replace individual BTF attributes (scope, msg_name_version, ...) on + # top of the mapped starter-set default. + def btf_upload(code, document, **overrides) + self.BTU(document, Epics::BtfMapping.upload(code, **overrides)) end - def btf_download(code, from, to) - btf = Epics::BtfMapping.download(code) + def btf_download(code, from, to, **overrides) + btf = Epics::BtfMapping.download(code, **overrides) if btf.container == 'ZIP' download_and_unzip(Epics::BTD, service: btf, from: from, to: to) else diff --git a/spec/btf_spec.rb b/spec/btf_spec.rb index 294fbafb..bd4a5f94 100644 --- a/spec/btf_spec.rb +++ b/spec/btf_spec.rb @@ -2,7 +2,7 @@ it 'normalizes attributes into the service hash consumed by HeaderRequest' do btf = described_class.new( service_name: 'SCT', scope: 'DE', service_option: 'URG', - container: 'ZIP', msg_name: 'pain.001', msg_version: '03', msg_variant: '001' + container: 'ZIP', msg_name: 'pain.001', msg_name_version: '03', msg_variant: '001' ) expect(btf.to_h).to eq( @@ -33,4 +33,18 @@ it 'raises a helpful error for unmapped codes' do expect { described_class.upload('ZZZ') }.to raise_error(ArgumentError, /raw/) end + + it 'applies overrides on top of the mapped defaults' do + btf = described_class.download('C53', scope: 'CH', msg_name_version: '04') + expect(btf.service_name).to eq('EOP') # default kept + expect(btf.msg_name).to eq('camt.053') # default kept + expect(btf.scope).to eq('CH') # overridden + expect(btf.msg_name_version).to eq('04') # overridden + end + + it 'ignores nil overrides, keeping the mapped default' do + btf = described_class.download('C53', scope: nil, msg_name_version: nil) + expect(btf.scope).to eq('DE') + expect(btf.msg_name_version).to eq('08') + end end diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index 3f02b41f..6c263b31 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -201,7 +201,7 @@ def cert_data(rsa) expect(services.map(&:service_name)).to eq(%w[EOP SCT]) expect(services.first.container).to eq('ZIP') expect(services.first.msg_name).to eq('camt.053') - expect(services.first.msg_version).to eq('08') + expect(services.first.msg_name_version).to eq('08') end end @@ -269,6 +269,14 @@ def cert_data(rsa) expect(order.header.to_s).to include('BTU') expect(order.header.to_s).to include('SCT') end + + it 'passes per-call overrides through to the rendered BTD Service' do + btf = Epics::BtfMapping.download('C53', scope: 'CH', msg_name_version: '04') + header = Epics::BTD.new(client, service: btf).header.to_s + expect(header).to include('EOP') + expect(header).to include('CH') + expect(header).to include('camt.053') + end end end diff --git a/spec/orders/btd_spec.rb b/spec/orders/btd_spec.rb index 5d0d6e50..f3ed4c2b 100644 --- a/spec/orders/btd_spec.rb +++ b/spec/orders/btd_spec.rb @@ -8,7 +8,7 @@ end let(:service) do - Epics::BTF.new(service_name: 'EOP', scope: 'DE', container: 'ZIP', msg_name: 'camt.053', msg_version: '08') + Epics::BTF.new(service_name: 'EOP', scope: 'DE', container: 'ZIP', msg_name: 'camt.053', msg_name_version: '08') end subject(:order) { described_class.new(client, service: service, from: '2026-01-01', to: '2026-01-31') } diff --git a/spec/orders/btu_spec.rb b/spec/orders/btu_spec.rb index 66089032..8ec11dbe 100644 --- a/spec/orders/btu_spec.rb +++ b/spec/orders/btu_spec.rb @@ -9,7 +9,7 @@ let(:document) { File.read(File.join(File.dirname(__FILE__), '..', 'fixtures', 'xml', 'cd1.xml')) } let(:service) do - Epics::BTF.new(service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_version: '03') + Epics::BTF.new(service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_name_version: '03') end subject(:order) { described_class.new(client, document, service: service) } From 1a2111af924e8b8ce96baac5ae0dfeadec4a4a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Tue, 7 Jul 2026 14:16:11 +0200 Subject: [PATCH 10/12] Raise on unsupported order types under H005 instead of malformed requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only ~10 classic order codes have an H005 branch. Any other business order (XE2, AZV, CIP, Z52-54, FDL, ...) fell through to the classic path, and because build_h005_order_details derives AdminOrderType from the order_type, it emitted e.g. XE2 with StandardOrderParams — an order the bank rejects, with no hint why. Add a central guard: business order codes reaching the H005 order-details builder now raise Epics::VersionSupportError pointing at the raw BTU/BTD API and BtfMapping. Administrative order types (HPB/HTD/HAA/HKD/HPD/HAC/ PTK/INI/HIA) and the BTU/BTD transports are whitelisted. H004 is unaffected (the H005 builder only runs under an H005 client). Co-Authored-By: Claude Opus 4.8 --- lib/epics.rb | 5 +++++ lib/epics/header_request.rb | 23 +++++++++++++++++++++++ spec/h005_client_spec.rb | 16 ++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/lib/epics.rb b/lib/epics.rb index 85ed1850..97567bf5 100644 --- a/lib/epics.rb +++ b/lib/epics.rb @@ -81,6 +81,11 @@ module Epics h004: { namespace: 'urn:org:ebics:H004', version: 'H004', revision: '1' }, h005: { namespace: 'urn:org:ebics:H005', version: 'H005', revision: '1' }, }.freeze + + # Raised when an order type is requested under an EBICS protocol version that + # does not support it (e.g. a classic H004 business order under H005, which + # expresses business transactions as BTF services instead). + class VersionSupportError < StandardError; end end Ebics = Epics diff --git a/lib/epics/header_request.rb b/lib/epics/header_request.rb index 766fbe57..0a6ac507 100644 --- a/lib/epics/header_request.rb +++ b/lib/epics/header_request.rb @@ -2,6 +2,12 @@ class Epics::HeaderRequest extend Forwardable attr_accessor :client + # Administrative order types that remain first-class order types under EBICS + # 3.0 (H005). Every other (business) transaction is a BTF service carried by + # BTU/BTD, so a classic business order code reaching the H005 builder is a + # caller error rather than something to silently emit as a malformed request. + H005_ADMIN_ORDER_TYPES = %w[HPB HTD HAA HKD HPD HAC PTK INI HIA].freeze + def initialize(client) self.client = client end @@ -61,6 +67,7 @@ def build_h004_order_details(xml, options) def build_h005_order_details(xml, options) admin_order_type = options[:admin_order_type] || options[:order_type] + ensure_h005_order_type!(admin_order_type) xml.AdminOrderType admin_order_type case admin_order_type @@ -97,6 +104,22 @@ def build_h005_order_details(xml, options) end end + # Reject classic business order codes under H005 instead of emitting an + # AdminOrderType the bank will reject. BTU/BTD are the generic upload/download + # transports; the whitelist covers the administrative order types that survive + # into H005. Everything else must go through a BTF service (see Epics::BtfMapping + # and Epics::Client#BTU / #BTD). + def ensure_h005_order_type!(admin_order_type) + return if %w[BTU BTD].include?(admin_order_type) + return if H005_ADMIN_ORDER_TYPES.include?(admin_order_type) + + raise Epics::VersionSupportError, + "Order type #{admin_order_type.inspect} is not available under EBICS 3.0 (H005). " \ + "H005 expresses business transactions as BTF services — use Epics::Client#BTU / #BTD " \ + "with an Epics::BTF, or add a mapping in Epics::BtfMapping. Administrative order types " \ + "supported under H005: #{H005_ADMIN_ORDER_TYPES.join(', ')}." + end + # Builds the BTF element. The child element order follows the H005 # schema sequence: ServiceName, Scope, ServiceOption, Container, MsgName. def build_service(xml, service) diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index 6c263b31..d8f18e2c 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -278,6 +278,22 @@ def cert_data(rsa) expect(header).to include('camt.053') end end + + describe 'rejects classic business orders that have no H005 form' do + it 'raises VersionSupportError instead of emitting a malformed order' do + expect { Epics::XE2.new(client, '').to_xml } + .to raise_error(Epics::VersionSupportError, /not available under EBICS 3.0/) + end + + it 'names the raw BTU/BTD escape hatch in the message' do + expect { Epics::Z53.new(client).to_xml } + .to raise_error(Epics::VersionSupportError, /BTU.*BTD|BtfMapping/) + end + + it 'still allows administrative order types (HTD)' do + expect { Epics::HTD.new(client).to_xml }.not_to raise_error + end + end end RSpec.describe 'EBICS 2.5 (H004) remains the default and unchanged' do From 647c4348d1ad8ed0c2a5bd929fd9e1b8139704f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Tue, 7 Jul 2026 14:29:43 +0200 Subject: [PATCH 11/12] Broaden H005 BTF coverage to more classic order codes Extend BtfMapping and wire the H005 branch into more convenience methods so they route to BTU/BTD instead of raising VersionSupportError. Service tuples follow railslove/epics PR #154's v3 factory: uploads: AZV (XCT/dtazv), C2S/CDS (SDD/BIL), CIP (SCI), XE2 (MCT), XE3 (SDD) downloads: Z52/Z53/Z54, Z01, BKA, C5N, CDZ, CRZ (EOP/STM/REP/PSR camt/pain) Message versions are only pinned where the DE default is well-known; elsewhere carries no version so the bank applies its own, and callers can override per call. Order codes with no H005 form (CD1, FUL, WSS, XCT, XDS) remain unmapped and are rejected by the guard. Co-Authored-By: Claude Opus 4.8 --- lib/epics/btf_mapping.rb | 19 ++++++++++- lib/epics/client.rb | 73 ++++++++++++++++++++++++++++++---------- spec/btf_spec.rb | 21 ++++++++++++ spec/h005_client_spec.rb | 18 ++++++++-- 4 files changed, 110 insertions(+), 21 deletions(-) diff --git a/lib/epics/btf_mapping.rb b/lib/epics/btf_mapping.rb index 1c45319f..135d0517 100644 --- a/lib/epics/btf_mapping.rb +++ b/lib/epics/btf_mapping.rb @@ -11,12 +11,21 @@ # # Unmapped codes raise, pointing the caller at the raw BTU/BTD API. module Epics::BtfMapping - # code => [direction, btf-attributes] + # code => btf-attributes. Message versions are only pinned where we are fairly + # confident of the DE default; where omitted the carries no version + # attribute and the bank applies its own default. Override per call when your + # bank differs (see Epics::Client convenience methods / #BTU / #BTD). UPLOADS = { 'CCT' => { service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_name_version: '03' }, 'CCS' => { service_name: 'SCT', scope: 'DE', msg_name: 'pain.001', msg_name_version: '03' }, 'CDD' => { service_name: 'SDD', service_option: 'COR', scope: 'DE', msg_name: 'pain.008', msg_name_version: '02' }, 'CDB' => { service_name: 'SDD', service_option: 'B2B', scope: 'DE', msg_name: 'pain.008', msg_name_version: '02' }, + 'AZV' => { service_name: 'XCT', scope: 'DE', msg_name: 'dtazv' }, + 'C2S' => { service_name: 'SDD', scope: 'BIL', msg_name: 'pain.008' }, + 'CDS' => { service_name: 'SDD', scope: 'BIL', msg_name: 'pain.008' }, + 'CIP' => { service_name: 'SCI', msg_name: 'pain.001' }, + 'XE2' => { service_name: 'MCT', msg_name: 'pain.001' }, + 'XE3' => { service_name: 'SDD', msg_name: 'pain.008' }, }.freeze DOWNLOADS = { @@ -26,6 +35,14 @@ module Epics::BtfMapping 'C54' => { service_name: 'REP', scope: 'DE', container: 'ZIP', msg_name: 'camt.054', msg_name_version: '08' }, 'VMK' => { service_name: 'STM', scope: 'DE', msg_name: 'mt942' }, 'PSR' => { service_name: 'PSR', scope: 'DE', msg_name: 'pain.002', msg_name_version: '03' }, + 'Z52' => { service_name: 'STM', container: 'ZIP', msg_name: 'camt.052' }, + 'Z53' => { service_name: 'EOP', container: 'ZIP', msg_name: 'camt.053' }, + 'Z54' => { service_name: 'EOP', service_option: 'XQRR', container: 'ZIP', msg_name: 'camt.054' }, + 'Z01' => { service_name: 'PSR', service_option: 'CH003GEN', container: 'ZIP', msg_name: 'pain.002' }, + 'BKA' => { service_name: 'EOP', scope: 'DE', container: 'ZIP', msg_name: 'camt.053' }, + 'C5N' => { service_name: 'STM', scope: 'DE', service_option: 'SCI', container: 'ZIP', msg_name: 'camt.054' }, + 'CDZ' => { service_name: 'REP', scope: 'DE', service_option: 'SDD', container: 'ZIP', msg_name: 'pain.002' }, + 'CRZ' => { service_name: 'REP', scope: 'DE', service_option: 'SCT', container: 'ZIP', msg_name: 'pain.002' }, }.freeze module_function diff --git a/lib/epics/client.rb b/lib/epics/client.rb index 1a31d384..f65907ad 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -5,9 +5,9 @@ class Epics::Client :x_509_certificates_content, :debug_mode, :ebics_version attr_writer :iban, :bic, :name - + def_delegators :connection, :post - + def initialize(keys_content, passphrase, url, host_id, user_id, partner_id, options = {}) self.keys_content = keys_content.respond_to?(:read) ? keys_content.read : keys_content if keys_content self.passphrase = passphrase @@ -178,7 +178,9 @@ def HPB [bank_x, bank_e] end - def AZV(document) + def AZV(document, **overrides) + return btf_upload('AZV', document, **overrides) if h005? + upload(Epics::AZV, document) end @@ -188,27 +190,37 @@ def CD1(document) def CDB(document, **overrides) return btf_upload('CDB', document, **overrides) if h005? + upload(Epics::CDB, document) end - def C2S(document) + def C2S(document, **overrides) + return btf_upload('C2S', document, **overrides) if h005? + upload(Epics::C2S, document) end def CDD(document, **overrides) return btf_upload('CDD', document, **overrides) if h005? + upload(Epics::CDD, document) end - def XE2(document) + def XE2(document, **overrides) + return btf_upload('XE2', document, **overrides) if h005? + upload(Epics::XE2, document) end - def XE3(document) + def XE3(document, **overrides) + return btf_upload('XE3', document, **overrides) if h005? + upload(Epics::XE3, document) end - def CDS(document) + def CDS(document, **overrides) + return btf_upload('CDS', document, **overrides) if h005? + upload(Epics::CDS, document) end @@ -218,15 +230,19 @@ def XDS(document) def CCT(document, **overrides) return btf_upload('CCT', document, **overrides) if h005? + upload(Epics::CCT, document) end - def CIP(document) + def CIP(document, **overrides) + return btf_upload('CIP', document, **overrides) if h005? + upload(Epics::CIP, document) end def CCS(document, **overrides) return btf_upload('CCS', document, **overrides) if h005? + upload(Epics::CCS, document) end @@ -252,6 +268,7 @@ def BTD(service, from: nil, to: nil, parameters: nil) def STA(from = nil, to = nil, **overrides) return btf_download('STA', from, to, **overrides) if h005? + download(Epics::STA, from: from, to: to) end @@ -261,53 +278,73 @@ def FDL(format, from = nil, to = nil) def VMK(from = nil, to = nil, **overrides) return btf_download('VMK', from, to, **overrides) if h005? + download(Epics::VMK, from: from, to: to) end - def CDZ(from = nil, to = nil) + def CDZ(from = nil, to = nil, **overrides) + return btf_download('CDZ', from, to, **overrides) if h005? + download_and_unzip(Epics::CDZ, from: from, to: to) end - def CRZ(from = nil, to = nil) + def CRZ(from = nil, to = nil, **overrides) + return btf_download('CRZ', from, to, **overrides) if h005? + download_and_unzip(Epics::CRZ, from: from, to: to) end - def BKA(from, to) + def BKA(from, to, **overrides) + return btf_download('BKA', from, to, **overrides) if h005? + download_and_unzip(Epics::BKA, from: from, to: to) end def C52(from, to, **overrides) return btf_download('C52', from, to, **overrides) if h005? + download_and_unzip(Epics::C52, from: from, to: to) end def C53(from, to, **overrides) return btf_download('C53', from, to, **overrides) if h005? + download_and_unzip(Epics::C53, from: from, to: to) end def C54(from, to, **overrides) return btf_download('C54', from, to, **overrides) if h005? + download_and_unzip(Epics::C54, from: from, to: to) end - def C5N(from, to) + def C5N(from, to, **overrides) + return btf_download('C5N', from, to, **overrides) if h005? + download_and_unzip(Epics::C5N, from: from, to: to) end - def Z01(from, to) + def Z01(from, to, **overrides) + return btf_download('Z01', from, to, **overrides) if h005? + download_and_unzip(Epics::Z01, from: from, to: to) end - def Z52(from, to) + def Z52(from, to, **overrides) + return btf_download('Z52', from, to, **overrides) if h005? + download_and_unzip(Epics::Z52, from: from, to: to) end - def Z53(from, to) + def Z53(from, to, **overrides) + return btf_download('Z53', from, to, **overrides) if h005? + download_and_unzip(Epics::Z53, from: from, to: to) end - def Z54(from, to) + def Z54(from, to, **overrides) + return btf_download('Z54', from, to, **overrides) if h005? + download_and_unzip(Epics::Z54, from: from, to: to) end @@ -363,7 +400,7 @@ def WSS def save_keys(path) File.write(path, dump_keys) end - + def x_509_certificate(type) content = x_509_certificates_content[type.to_sym] if content.nil? || content.empty? @@ -375,7 +412,7 @@ def x_509_certificate(type) end Epics::X509Certificate.new(content) end - + def x_509_certificate_hash(type) # Routes through x_509_certificate so H005 self-signed certificates get a # fingerprint too — the INI letter needs it for subscriber verification. diff --git a/spec/btf_spec.rb b/spec/btf_spec.rb index bd4a5f94..e94dc0fd 100644 --- a/spec/btf_spec.rb +++ b/spec/btf_spec.rb @@ -30,10 +30,31 @@ expect(btf.msg_name).to eq('camt.053') end + it 'maps the extended upload set (XE2/AZV/CIP/...)' do + expect(described_class.upload('XE2').service_name).to eq('MCT') + expect(described_class.upload('AZV').msg_name).to eq('dtazv') + expect(described_class.upload('CIP').service_name).to eq('SCI') + expect(described_class.upload('C2S').scope).to eq('BIL') + end + + it 'maps the extended download set (Z53/BKA/CDZ/...)' do + expect(described_class.download('Z53').service_name).to eq('EOP') + expect(described_class.download('Z53').container).to eq('ZIP') + expect(described_class.download('CDZ').service_option).to eq('SDD') + expect(described_class.download('CRZ').service_option).to eq('SCT') + expect(described_class.download('C5N').service_option).to eq('SCI') + end + it 'raises a helpful error for unmapped codes' do expect { described_class.upload('ZZZ') }.to raise_error(ArgumentError, /raw/) end + it 'still raises for codes with no H005 form (CD1/FUL/XCT/XDS)' do + %w[CD1 FUL XCT XDS].each do |code| + expect { described_class.upload(code) }.to raise_error(ArgumentError) + end + end + it 'applies overrides on top of the mapped defaults' do btf = described_class.download('C53', scope: 'CH', msg_name_version: '04') expect(btf.service_name).to eq('EOP') # default kept diff --git a/spec/h005_client_spec.rb b/spec/h005_client_spec.rb index d8f18e2c..9e93ba44 100644 --- a/spec/h005_client_spec.rb +++ b/spec/h005_client_spec.rb @@ -281,12 +281,12 @@ def cert_data(rsa) describe 'rejects classic business orders that have no H005 form' do it 'raises VersionSupportError instead of emitting a malformed order' do - expect { Epics::XE2.new(client, '').to_xml } + expect { Epics::WSS.new(client).to_xml } .to raise_error(Epics::VersionSupportError, /not available under EBICS 3.0/) end it 'names the raw BTU/BTD escape hatch in the message' do - expect { Epics::Z53.new(client).to_xml } + expect { Epics::FUL.new(client, '').to_xml } .to raise_error(Epics::VersionSupportError, /BTU.*BTD|BtfMapping/) end @@ -294,6 +294,20 @@ def cert_data(rsa) expect { Epics::HTD.new(client).to_xml }.not_to raise_error end end + + describe 'extended convenience methods route to BTU/BTD under H005' do + it 'XE2 builds a BTU with the MCT service' do + order = Epics::BTU.new(client, '', service: Epics::BtfMapping.upload('XE2')) + expect(order.header.to_s).to include('BTU') + expect(order.header.to_s).to include('MCT') + end + + it 'Z53 builds a BTD with the EOP service' do + order = Epics::BTD.new(client, service: Epics::BtfMapping.download('Z53')) + expect(order.header.to_s).to include('BTD') + expect(order.header.to_s).to include('EOP') + end + end end RSpec.describe 'EBICS 2.5 (H004) remains the default and unchanged' do From 8e471a8471fed51c74a0e1c057c9ecf7866935bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxim=20K=C3=BCpper?= Date: Tue, 7 Jul 2026 14:45:36 +0200 Subject: [PATCH 12/12] Centralize the A006 signature version behind a constant The signature version 'A006' was duplicated across the INI order data, the H005 DataDigest, the user-signature block, the key accessor, setup, and the cert-type map. Extract Epics::SIGNATURE_VERSION and document the gem's deliberate A006-only design (RSASSA-PSS; X002 auth stays PKCS#1 v1.5 on its own path, legacy A005 is unsupported). Move the Epics module constants above the epics/* requires so load-time references (e.g. KEY_FOR_CERT_TYPE) resolve. Co-Authored-By: Claude Opus 4.8 --- lib/epics.rb | 46 +++++++++++++++++------------ lib/epics/client.rb | 6 ++-- lib/epics/generic_upload_request.rb | 4 +-- lib/epics/ini.rb | 4 +-- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/lib/epics.rb b/lib/epics.rb index 97567bf5..85db7476 100644 --- a/lib/epics.rb +++ b/lib/epics.rb @@ -9,6 +9,33 @@ require 'faraday' require 'securerandom' require 'time' + +module Epics + DEFAULT_PRODUCT_NAME = 'EPICS - a ruby ebics kernel' + DEFAULT_LOCALE = :de + DEFAULT_VERSION = :h004 + + # The electronic-signature version used for the user (bank-technical) key. + # This gem is A006-only: A006 (RSASSA-PSS, SHA-256) is signed by Epics::Key#sign + # and declared in every SignatureVersion element. A006 is valid under both + # H004 and H005 (which mandates it); the legacy A005 (RSA PKCS#1 v1.5) is not + # supported — the authentication key X002 stays PKCS#1 v1.5 on its own path. + SIGNATURE_VERSION = 'A006' + + # EBICS protocol version descriptors. The gem defaults to H004 (EBICS 2.5) so + # that existing users are unaffected; H005 (EBICS 3.0) is opt-in via the + # `version:` client option. + EBICS_PROTOCOLS = { + h004: { namespace: 'urn:org:ebics:H004', version: 'H004', revision: '1' }, + h005: { namespace: 'urn:org:ebics:H005', version: 'H005', revision: '1' }, + }.freeze + + # Raised when an order type is requested under an EBICS protocol version that + # does not support it (e.g. a classic H004 business order under H005, which + # expresses business transactions as BTF services instead). + class VersionSupportError < StandardError; end +end + require "epics/version" require "epics/key" require "epics/response" @@ -69,23 +96,4 @@ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'letter/locales', '*.yml')] -module Epics - DEFAULT_PRODUCT_NAME = 'EPICS - a ruby ebics kernel' - DEFAULT_LOCALE = :de - DEFAULT_VERSION = :h004 - - # EBICS protocol version descriptors. The gem defaults to H004 (EBICS 2.5) so - # that existing users are unaffected; H005 (EBICS 3.0) is opt-in via the - # `version:` client option. - EBICS_PROTOCOLS = { - h004: { namespace: 'urn:org:ebics:H004', version: 'H004', revision: '1' }, - h005: { namespace: 'urn:org:ebics:H005', version: 'H005', revision: '1' }, - }.freeze - - # Raised when an order type is requested under an EBICS protocol version that - # does not support it (e.g. a classic H004 business order under H005, which - # expresses business transactions as BTF services instead). - class VersionSupportError < StandardError; end -end - Ebics = Epics diff --git a/lib/epics/client.rb b/lib/epics/client.rb index f65907ad..bb1f4716 100644 --- a/lib/epics/client.rb +++ b/lib/epics/client.rb @@ -62,7 +62,7 @@ def e end def a - keys["A006"] + keys[Epics::SIGNATURE_VERSION] end def x @@ -111,7 +111,7 @@ def services def self.setup(passphrase, url, host_id, user_id, partner_id, keysize = 2048, options = {}) client = new(nil, passphrase, url, host_id, user_id, partner_id, options) - client.keys = %w(A006 X002 E002).each_with_object({}) do |type, memo| + client.keys = [Epics::SIGNATURE_VERSION, 'X002', 'E002'].each_with_object({}) do |type, memo| memo[type] = Epics::Key.new( OpenSSL::PKey::RSA.generate(keysize) ) end @@ -485,7 +485,7 @@ def rsa_from_modulus_exponent(modulus, exponent) OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der) end - KEY_FOR_CERT_TYPE = { a: 'A006', x: 'X002', e: 'E002' }.freeze + KEY_FOR_CERT_TYPE = { a: Epics::SIGNATURE_VERSION, x: 'X002', e: 'E002' }.freeze def self_signed_certificate(type) key_name = KEY_FOR_CERT_TYPE.fetch(type.to_sym) diff --git a/lib/epics/generic_upload_request.rb b/lib/epics/generic_upload_request.rb index 508ecba9..b63267f0 100644 --- a/lib/epics/generic_upload_request.rb +++ b/lib/epics/generic_upload_request.rb @@ -29,7 +29,7 @@ def body xml.SignatureData(encrypted_order_signature, authenticate: true) # EBICS 3.0 (H005) additionally carries the plain hash of the order # data (the value that was signed) as a DataDigest element. - xml.DataDigest(data_digest, SignatureVersion: 'A006') if client.h005? + xml.DataDigest(data_digest, SignatureVersion: Epics::SIGNATURE_VERSION) if client.h005? } } end.doc.root @@ -50,7 +50,7 @@ def order_signature Nokogiri::XML::Builder.new do |xml| xml.UserSignatureData('xmlns' => ns, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation' => "#{ns} #{ns}/ebics_signature.xsd") { xml.OrderSignatureData { - xml.SignatureVersion "A006" + xml.SignatureVersion Epics::SIGNATURE_VERSION xml.SignatureValue signature_value xml.PartnerID partner_id xml.UserID user_id diff --git a/lib/epics/ini.rb b/lib/epics/ini.rb index c5d79bc5..1946e316 100644 --- a/lib/epics/ini.rb +++ b/lib/epics/ini.rb @@ -38,7 +38,7 @@ def key_signature } xml.TimeStamp timestamp } - xml.SignatureVersion 'A006' + xml.SignatureVersion Epics::SIGNATURE_VERSION } xml.PartnerID partner_id xml.UserID user_id @@ -54,7 +54,7 @@ def h005_key_signature xml.SignaturePubKeyOrderData('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'http://www.ebics.org/S002') { xml.SignaturePubKeyInfo { x509_data_xml(xml, client.x_509_certificate(:a)) - xml.SignatureVersion 'A006' + xml.SignatureVersion Epics::SIGNATURE_VERSION } xml.PartnerID partner_id xml.UserID user_id