epics 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc38fae79ba6b5ebcbafa535bfd7b4364b708deff8d1d45caa44ed9273a338e1
4
- data.tar.gz: 3a54d33face295d3f0718baeb6343d8aff05946b27aaf11f9df0871869e266bf
3
+ metadata.gz: 1a9354e4e83108afe41607057bd8ce1fcba27fc03476a521269911f1f1fea57f
4
+ data.tar.gz: 82333eed938cdb0a31fcb54b206a6c5653eef950ee7b120f9487de012e4b22f0
5
5
  SHA512:
6
- metadata.gz: 3827df11227abf25613c8b27356cbb45465d2be54b58421725bb9625338be4c1bedab8683c3b0f9e9630bc3a63700bfcc897fff130baae465475e53f5112aa43
7
- data.tar.gz: 168a79d4581dcc1053a130081d70bf08741f7d7f96582863963653aab189f866c303b6c582d15584851443d5c65756139b3956ca81c3dfbf0715afc5ba3040ae
6
+ metadata.gz: 9d74d5100f8698e179df3d9b7eb3f3b3154d64313b975e6645b99ebdfd389e6b1695021640f39b753c685d47055216bf2ecda404c01b1c32f049f16b4caae769
7
+ data.tar.gz: 3d79c5800b76e7dfbe259e27e8ae1d1f14a77dc99a5b676603bd184a3cff5d3780682f13b14c796a0f92182805cec119ddf4dd585977b4b81f2c0195df00cd2d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ### Unreleased
2
2
 
3
+ ### 2.11.0
4
+
5
+ - [ENHANCEMENT] Added FUL order type (thanks to @scollon-pl)
6
+
7
+ ### 2.10.0
8
+
9
+ - [ENHANCEMENT] Added X.509 certificates support for INI and HIA (thanks to @vnoviskyi)
10
+ - [ENHANCEMENT] Added Z01 order type (thanks to @Nymuxyzo)
11
+
3
12
  ### 2.9.0
4
13
 
5
14
  - [ENHANCEMENT] Added HEV order type (thanks to @jplot)
data/README.md CHANGED
@@ -10,7 +10,7 @@ It supports EBICS 2.5.
10
10
 
11
11
  The client supports the complete initialization process comprising INI, HIA and HPB including the
12
12
  INI letter generation. It offers support for the most common download and upload order types
13
- (STA HAA HTD HPD PTK HAC HKD BKA C52 C53 C54 CD1 CDB CDD CCT VMK FDL).
13
+ (STA HAA HTD HPD PTK HAC HKD BKA C52 C53 C54 CD1 CDB CDD CCT VMK FDL FUL).
14
14
 
15
15
  ## Installation
16
16
 
@@ -41,7 +41,7 @@ Once the paperwork is done, your bank should provide you with:
41
41
  Take these parameters and start setting up an UserID (repeat this for every user you want to initialize):
42
42
 
43
43
  ```ruby
44
- e = Epics::Client.setup("my-super-secret", "https://ebics.sandbox", "EBICS_HOST_ID", "EBICS_USER_ID", "EBICS_PARTNER_ID")
44
+ e = Epics::Client.setup("my-super-secret", "https://ebics.sandbox", "EBICS_HOST_ID", "EBICS_USER_ID", "EBICS_PARTNER_ID", 4096)
45
45
  ```
46
46
 
47
47
  To use the keys later, just store them in a file
@@ -200,6 +200,76 @@ that are hiding some strange names from you:
200
200
  If you need more sophisticated EBICS order types, please read the next section
201
201
  about the supported functionalities.
202
202
 
203
+ ### Using X.509 Certificates
204
+
205
+ Epics supports using X.509 self-signed certificates for INI and HIA requests, as required by some banks. This is in addition to the classic key-based workflow.
206
+
207
+ #### When to Use
208
+
209
+ Some banks require X.509 certificates for EBICS initialization (INI/HIA).
210
+
211
+ You can generate your own X.509 certificate using Ruby’s OpenSSL library:
212
+
213
+ This examples showcases the generation of the X.509 certificate A file and can be applied the same way for the others.
214
+ ```ruby
215
+ key = client.a.key # or e key, or x key
216
+ name = OpenSSL::X509::Name.parse('/CN=Test Certificate/O=MyOrg/C=DE')
217
+ cert = OpenSSL::X509::Certificate.new
218
+ cert.version = 2
219
+ cert.serial = SecureRandom.random_number(2**64)
220
+ cert.subject = name
221
+ cert.issuer = name
222
+ cert.public_key = key.public_key
223
+ cert.not_before = Time.current
224
+ cert.not_after = cert.not_before + 1.year
225
+
226
+ ef = OpenSSL::X509::ExtensionFactory.new
227
+ ef.subject_certificate = cert
228
+ ef.issuer_certificate = cert
229
+ cert.add_extension(ef.create_extension('basicConstraints', 'CA:FALSE', true))
230
+ cert.add_extension(ef.create_extension('keyUsage', 'digitalSignature,nonRepudiation,keyEncipherment', true))
231
+
232
+ cert.sign(key, OpenSSL::Digest.new('SHA256'))
233
+ cert
234
+
235
+ # Save to file
236
+ File.write("cert_a.pem", cert.to_pem)
237
+ ```
238
+ You can now use the contents of the generated certificate file in PEM format as your
239
+ `x_509_certificate_a_content`, `x_509_certificate_x_content`, or `x_509_certificate_e_content`
240
+ in the client initialization.
241
+
242
+ **Note:** For production environments, your bank may require certificates issued by a trusted authority. Be sure to confirm your bank’s requirements before proceeding.
243
+
244
+ #### Initializing the Client with X.509 Certificates
245
+ ```ruby
246
+ # Load your certificate data (PEM or DER encoded)
247
+ certificate_a = File.read("cert_a.pem")
248
+ certificate_x = File.read("cert_x.pem")
249
+ certificate_e = File.read("cert_e.pem")
250
+
251
+ client = Epics::Client.new(
252
+ keys, # your key data as before
253
+ 'passphrase',
254
+ 'url',
255
+ 'host',
256
+ 'user',
257
+ 'partner',
258
+ x_509_certificate_a_content: certificate_a,
259
+ x_509_certificate_x_content: certificate_x,
260
+ x_509_certificate_e_content: certificate_e,
261
+ debug_mode: true # Optional: enables verbose logging of EBICS requests/responses
262
+ )
263
+ ```
264
+ ### Example: Generating the Initialization Letter with Certificates
265
+
266
+ ```ruby
267
+ renderer = Epics::LetterRenderer.new(client)
268
+ letter = renderer.render("Your Bank Name")
269
+ File.write("initialization_letter.txt", letter)
270
+ ```
271
+ If all three certificates are present, the INI letter will use certificate hashes as required for certificate-based registration.
272
+
203
273
  ## Issues and Feature Requests
204
274
 
205
275
  [Railslove](http://railslove.com) is commited to provide the best developer tools for integrating
@@ -258,8 +328,8 @@ EPICS_VERIFY_SSL=false
258
328
  ## Contributing
259
329
  Railslove has a [Contributor License Agreement (CLA)](https://github.com/railslove/epics/blob/master/CONTRIBUTING.md) which clarifies the intellectual property rights for contributions from individuals or entities. To ensure every developer has signed the CLA, we use [CLA Assistant](https://cla-assistant.io/).
260
330
 
261
- After checking out the repo, run `bin/setup` to install dependencies.
262
- Then, run `rspec` to run the tests.
331
+ After checking out the repo, run `bin/setup` to install dependencies.
332
+ Then, run `rspec` to run the tests.
263
333
  You can also run `bin/console` for an interactive prompt that will allow you to experiment.
264
334
 
265
335
  0. Contact team@railslove.com for information about the CLA
data/lib/epics/client.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  class Epics::Client
2
2
  extend Forwardable
3
3
 
4
- attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :keys, :keys_content, :locale, :product_name
5
- attr_writer :iban, :bic, :name
4
+ attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :keys, :keys_content, :locale, :product_name,
5
+ :x_509_certificates_content, :debug_mode
6
6
 
7
+ attr_writer :iban, :bic, :name
8
+
7
9
  def_delegators :connection, :post
8
-
9
- def initialize(keys_content, passphrase, url, host_id, user_id, partner_id, locale: Epics::DEFAULT_LOCALE, product_name: Epics::DEFAULT_PRODUCT_NAME)
10
+
11
+ def initialize(keys_content, passphrase, url, host_id, user_id, partner_id, options = {})
10
12
  self.keys_content = keys_content.respond_to?(:read) ? keys_content.read : keys_content if keys_content
11
13
  self.passphrase = passphrase
12
14
  self.keys = extract_keys if keys_content
@@ -14,8 +16,14 @@ class Epics::Client
14
16
  self.host_id = host_id
15
17
  self.user_id = user_id
16
18
  self.partner_id = partner_id
17
- self.locale = locale
18
- self.product_name = product_name
19
+ self.locale = options[:locale] || Epics::DEFAULT_LOCALE
20
+ self.product_name = options[:product_name] || Epics::DEFAULT_PRODUCT_NAME
21
+ self.debug_mode = !!options[:debug_mode]
22
+ self.x_509_certificates_content = {
23
+ a: options[:x_509_certificate_a_content],
24
+ x: options[:x_509_certificate_x_content],
25
+ e: options[:x_509_certificate_e_content]
26
+ }
19
27
  end
20
28
 
21
29
  def inspect
@@ -61,8 +69,8 @@ class Epics::Client
61
69
  @order_types ||= (self.HTD; @order_types)
62
70
  end
63
71
 
64
- def self.setup(passphrase, url, host_id, user_id, partner_id, keysize = 2048)
65
- client = new(nil, passphrase, url, host_id, user_id, partner_id)
72
+ def self.setup(passphrase, url, host_id, user_id, partner_id, keysize = 2048, options = {})
73
+ client = new(nil, passphrase, url, host_id, user_id, partner_id, options)
66
74
  client.keys = %w(A006 X002 E002).each_with_object({}) do |type, memo|
67
75
  memo[type] = Epics::Key.new( OpenSSL::PKey::RSA.generate(keysize) )
68
76
  end
@@ -185,6 +193,10 @@ class Epics::Client
185
193
  upload(Epics::XCT, document)
186
194
  end
187
195
 
196
+ def FUL(document)
197
+ upload(Epics::FUL, document)
198
+ end
199
+
188
200
  def STA(from = nil, to = nil)
189
201
  download(Epics::STA, from: from, to: to)
190
202
  end
@@ -225,6 +237,10 @@ class Epics::Client
225
237
  download_and_unzip(Epics::C5N, from: from, to: to)
226
238
  end
227
239
 
240
+ def Z01(from, to)
241
+ download_and_unzip(Epics::Z01, from: from, to: to)
242
+ end
243
+
228
244
  def Z52(from, to)
229
245
  download_and_unzip(Epics::Z52, from: from, to: to)
230
246
  end
@@ -273,6 +289,19 @@ class Epics::Client
273
289
  def save_keys(path)
274
290
  File.write(path, dump_keys)
275
291
  end
292
+
293
+ def x_509_certificate(type)
294
+ content = x_509_certificates_content[type.to_sym]
295
+ return if content.nil? || content.empty?
296
+ Epics::X509Certificate.new(content)
297
+ end
298
+
299
+ def x_509_certificate_hash(type)
300
+ content = x_509_certificates_content[type.to_sym]
301
+ return if content.nil? || content.empty?
302
+ cert = OpenSSL::X509::Certificate.new(content)
303
+ Digest::SHA256.hexdigest(cert.to_der).upcase
304
+ end
276
305
 
277
306
  private
278
307
 
@@ -313,7 +342,7 @@ class Epics::Client
313
342
  faraday.use Epics::XMLSIG, { client: self }
314
343
  faraday.use Epics::ParseEbics, { client: self}
315
344
  # faraday.use MyAdapter
316
- # faraday.response :logger # log requests to STDOUT
345
+ faraday.response :logger, ::Logger.new(STDOUT), bodies: true if debug_mode # log requests/response to STDOUT
317
346
  end
318
347
  end
319
348
 
data/lib/epics/ful.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Epics::FUL < Epics::GenericUploadRequest
4
+ def header
5
+ ful_order_params = Hash.new.tap do |params|
6
+ params[:FileFormat] = options[:file_format]
7
+ end
8
+
9
+ client.header_request.build(
10
+ nonce: nonce,
11
+ timestamp: timestamp,
12
+ order_type: 'FUL',
13
+ order_attribute: 'DZHNN',
14
+ custom_order_params: { FULOrderParams: ful_order_params },
15
+ mutable: { TransactionPhase: 'Initialisation' }
16
+ )
17
+ end
18
+ end
@@ -105,4 +105,18 @@ class Epics::GenericRequest
105
105
  }
106
106
  end.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML, encoding: 'utf-8')
107
107
  end
108
+
109
+ private
110
+
111
+ def x509_data_xml(xml, x_509_certificate)
112
+ return unless x_509_certificate
113
+
114
+ xml.send('ds:X509Data') do
115
+ xml.send('ds:X509IssuerSerial') do
116
+ xml.send('ds:X509IssuerName', x_509_certificate.issuer)
117
+ xml.send('ds:X509SerialNumber', x_509_certificate.version)
118
+ end
119
+ xml.send('ds:X509Certificate', x_509_certificate.data)
120
+ end
121
+ end
108
122
  end
data/lib/epics/hia.rb CHANGED
@@ -26,6 +26,7 @@ class Epics::HIA < Epics::GenericRequest
26
26
  Nokogiri::XML::Builder.new do |xml|
27
27
  xml.HIARequestOrderData('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'urn:org:ebics:H004') {
28
28
  xml.AuthenticationPubKeyInfo {
29
+ x509_data_xml(xml, client.x_509_certificate(:x))
29
30
  xml.PubKeyValue {
30
31
  xml.send('ds:RSAKeyValue') {
31
32
  xml.send('ds:Modulus', Base64.strict_encode64([client.x.n].pack("H*")))
@@ -35,6 +36,7 @@ class Epics::HIA < Epics::GenericRequest
35
36
  xml.AuthenticationVersion 'X002'
36
37
  }
37
38
  xml.EncryptionPubKeyInfo{
39
+ x509_data_xml(xml, client.x_509_certificate(:e))
38
40
  xml.PubKeyValue {
39
41
  xml.send('ds:RSAKeyValue') {
40
42
  xml.send('ds:Modulus', Base64.strict_encode64([client.e.n].pack("H*")))
data/lib/epics/ini.rb CHANGED
@@ -26,6 +26,7 @@ class Epics::INI < Epics::GenericRequest
26
26
  Nokogiri::XML::Builder.new do |xml|
27
27
  xml.SignaturePubKeyOrderData('xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'xmlns' => 'http://www.ebics.org/S001') {
28
28
  xml.SignaturePubKeyInfo {
29
+ x509_data_xml(xml, client.x_509_certificate(:a))
29
30
  xml.PubKeyValue {
30
31
  xml.send('ds:RSAKeyValue') {
31
32
  xml.send('ds:Modulus', Base64.strict_encode64([client.a.n].pack("H*")))
@@ -1,7 +1,6 @@
1
1
  class Epics::LetterRenderer
2
2
  extend Forwardable
3
3
 
4
- TEMPLATE_PATH = File.join(File.dirname(__FILE__), '../letter/', 'ini.erb')
5
4
  I18N_SCOPE = 'epics.letter'
6
5
 
7
6
  def initialize(client)
@@ -12,11 +11,32 @@ class Epics::LetterRenderer
12
11
  I18n.translate(key, **{ locale: @client.locale, scope: I18N_SCOPE }.merge(options))
13
12
  end
14
13
 
15
- alias_method :t, :translate
14
+ alias t translate
16
15
 
17
16
  def_delegators :@client, :host_id, :user_id, :partner_id, :a, :x, :e
18
17
 
19
18
  def render(bankname)
20
- ERB.new(File.read(TEMPLATE_PATH)).result(binding)
19
+ template_path = File.join(File.dirname(__FILE__), '../letter/', template_filename)
20
+ ERB.new(File.read(template_path)).result(binding)
21
+ end
22
+
23
+ def template_filename
24
+ use_x_509_certificate_template? ? 'ini_with_certs.erb' : 'ini.erb'
25
+ end
26
+
27
+ def use_x_509_certificate_template?
28
+ x_509_certificate_a_hash && x_509_certificate_x_hash && x_509_certificate_e_hash
29
+ end
30
+
31
+ def x_509_certificate_a_hash
32
+ @client.x_509_certificate_hash(:a)
33
+ end
34
+
35
+ def x_509_certificate_x_hash
36
+ @client.x_509_certificate_hash(:x)
37
+ end
38
+
39
+ def x_509_certificate_e_hash
40
+ @client.x_509_certificate_hash(:e)
21
41
  end
22
42
  end
data/lib/epics/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Epics
4
- VERSION = '2.9.0'
4
+ VERSION = '2.11.0'
5
5
  end
@@ -0,0 +1,15 @@
1
+ class Epics::X509Certificate
2
+ extend Forwardable
3
+
4
+ attr_reader :certificate
5
+
6
+ def_delegators :certificate, :issuer, :version
7
+
8
+ def initialize(crt_content)
9
+ @certificate = OpenSSL::X509::Certificate.new(crt_content)
10
+ end
11
+
12
+ def data
13
+ Base64.strict_encode64(@certificate.to_der)
14
+ end
15
+ end
data/lib/epics/z01.rb ADDED
@@ -0,0 +1,17 @@
1
+ class Epics::Z01 < Epics::GenericRequest
2
+ def header
3
+ client.header_request.build(
4
+ nonce: nonce,
5
+ timestamp: timestamp,
6
+ order_type: 'Z01',
7
+ order_attribute: 'DZHNN',
8
+ order_params: {
9
+ DateRange: {
10
+ Start: options[:from],
11
+ End: options[:to]
12
+ }
13
+ },
14
+ mutable: { TransactionPhase: 'Initialisation' }
15
+ )
16
+ end
17
+ end
data/lib/epics.rb CHANGED
@@ -26,12 +26,14 @@ require "epics/htd"
26
26
  require "epics/haa"
27
27
  require "epics/sta"
28
28
  require "epics/fdl"
29
+ require "epics/ful"
29
30
  require "epics/vmk"
30
31
  require "epics/bka"
31
32
  require "epics/c52"
32
33
  require "epics/c53"
33
34
  require "epics/c54"
34
35
  require "epics/c5n"
36
+ require "epics/z01"
35
37
  require "epics/z52"
36
38
  require "epics/z53"
37
39
  require "epics/z54"
@@ -58,6 +60,7 @@ require "epics/hia"
58
60
  require "epics/ini"
59
61
  require "epics/hev"
60
62
  require "epics/signer"
63
+ require "epics/x_509_certificate"
61
64
  require "epics/client"
62
65
 
63
66
  I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'letter/locales', '*.yml')]