epics 3.0.0.rc2 → 3.0.1

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: 2d81d3bda7eef2d4468cba9327d5e72ad7222eec73e76bfe82c609765a212075
4
- data.tar.gz: b49bcb37a43532a0f9d5074478e22cfa126c4b3e7bf594894f8be27e60aeca37
3
+ metadata.gz: f51f635b331253de3ad314121ac6e0cf657a285415ae7811e986fb6e718649a4
4
+ data.tar.gz: 7889e2e42721731b46886fee4e47b20bdfb0b7a1ed892aace84886aa25edaf47
5
5
  SHA512:
6
- metadata.gz: '09ce2c4c8afa08bf91a00112d8a1078b388a76b40276006706f0c821a1aab7924655a9de56032956d1e74975dda1be8438e42d11211fae95b2d4aa4634b7f7f5'
7
- data.tar.gz: fe3a33f8d19057cbe437189ae98b18e32eafbedde991bb12b1cf0c10a63384a5910f254272f4d9d43f972951d50fa18601bd2b521253603b2f3893e6a81c9e13
6
+ metadata.gz: 7a9213441874971f2d932f36eb0032ef1ecc2063bc9567f5cd036c7dca596088e1e372caf3c10eb7ce0da749015781ad2838cc458433cbf684ecfacc5cc42e3f
7
+ data.tar.gz: 7deab543f728a21a132d63972ef8d8dcbfb659ff22ecccbf2e89f78c1b2666814475c89caace7b485168b08132a5c400bf030e2f1d34610cc6cd25c8e76dd836
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  ### Unreleased
2
2
 
3
+ ### 3.0.1
4
+
5
+ - [FIX] Upload order data was transmitted with all CR and LF removed, not as given. Any
6
+ content hashed by the caller no longer matched, so container uploads (e.g. `CCS`, `CDS`,
7
+ `C2S` with a `conxml` container whose `HashValue` covers the canonical pain message) were
8
+ rejected by the bank, and line breaks inside text values were lost. As in 2.x, the order
9
+ data is now sent unchanged and CR and LF are removed only to form the ES (electronic
10
+ signature) hash, as EBICS specifies; the bank removes them the same way before verifying.
11
+ This reverts the 3.0.0.rc1 entry "Upload order data is now normalized once"
12
+
13
+ ### 3.0.0
14
+
15
+ - [FIX] H005 uploads were sent without `SignatureFlag` in `BTUOrderParams`, although they
16
+ carry the user's ES (electronic signature), so banks treated them as orders
17
+ to be authorised outside EBICS and some rejected them with `EBICS_INVALID_ORDER_TYPE`.
18
+ `SignatureFlag` is now set by default, matching `OZHNN` on H004. Pass `request_eds: true`
19
+ to spool the order into the EDS (distributed electronic signature, German VEU)
20
+ when signatures are still missing, or `signature_flag: false` to omit the flag
21
+
22
+ ### 3.0.0.rc2
23
+
3
24
  - [FIX] A download spanning several segments returned only its first segment, so zipped
4
25
  downloads such as `C52` and `C53` raised `Zip::Error` and plain ones such as `STA` came
5
26
  back cut off without an error. The remaining segments are now requested in the transfer
@@ -27,6 +48,24 @@ rewrite of request building into builders/factories/handlers.
27
48
  - [BREAKING] On H005, `Client#HAA` and `Client#order_types` return an array of service
28
49
  hashes (`service_name`, `scope`, `service_option`, `container`, `msg_name`) instead of
29
50
  an array of order-type strings
51
+ - [BREAKING] `XEK`, `YCT` and `ZSR` raise `Epics::VersionSupportError` on H003/H004
52
+ instead of `NotImplementedError`, matching every other version mismatch
53
+ - [BREAKING] `order_id:` is validated against the EBICS `OrderIDType` range (`A000` to
54
+ `ZZZZ`). Values below `A000` rendered as e.g. `0002`, which violates the
55
+ `[A-Z][A-Z0-9]{3}` pattern; they now raise `ArgumentError`. The protocol form
56
+ (`order_id: 'A000'`) is accepted alongside the integer
57
+ - [BREAKING] Removed `Epics::Builders::MutableBuilder#add_receipt_code`, which emitted
58
+ `ReceiptCode` inside `mutable` where the schema does not allow it. `ReceiptCode`
59
+ belongs to `TransferReceipt`, which `TransferReceiptBuilder` already builds
60
+ - [BREAKING] Upload order data is now normalized once, on the way in, so the bytes
61
+ hashed for the electronic signature are the bytes transmitted. Until 3.0 the digest
62
+ was taken over the normalized document while the raw one was sent, which only
63
+ verifies against banks that normalize before checking the ES
64
+ - [BREAKING] Passing only one date-range bound (e.g. `client.STA(from)`) silently
65
+ omitted the range and fetched the bank's full retention window. Both bounds are
66
+ mandatory within `DateRange`, so this now raises `ArgumentError`
67
+ - [BREAKING] Removed `Epics::Services::CryptService#sign`, which had no callers and
68
+ disagreed with `#encrypt` on how an A005 signature is formed
30
69
 
31
70
  **Fixes**
32
71
 
@@ -41,17 +80,12 @@ rewrite of request building into builders/factories/handlers.
41
80
  when the key they need was not loaded; they now raise `Epics::MissingKeyError` naming
42
81
  it. `#digest_valid?` no longer needs a key at all — it is a plain SHA-256 over the
43
82
  authenticated nodes
44
- - [BREAKING] Removed `Epics::Builders::MutableBuilder#add_receipt_code`, which emitted
45
- `ReceiptCode` inside `mutable` where the schema does not allow it. `ReceiptCode`
46
- belongs to `TransferReceipt`, which `TransferReceiptBuilder` already builds
47
83
  - [FIX] Removed the `container_flag:` order parameter, which emitted a `ContainerFlag`
48
84
  element that exists in no EBICS schema. The container is expressed by `Container`
49
85
  with a `containerType` attribute, which `container_type:` already emits
50
86
  - [ENHANCEMENT] Added the H005-only order types `XEK` (account statements as PDF),
51
87
  `ZSR` (payment status reports) and `YCT` (multi-currency credit transfer). Their BTF
52
88
  mappings existed in the V3 factory but had no order class or client method
53
- - [BREAKING] `XEK`, `YCT` and `ZSR` raise `Epics::VersionSupportError` on H003/H004
54
- instead of `NotImplementedError`, matching every other version mismatch
55
89
  - [ENHANCEMENT] `XEK`, `ZSR`, `Z01`, `Z52`, `Z53` and `Z54` accept `scope:`,
56
90
  `service_option:`, `msg_name_version:` and `container_type:`. Defaults are unchanged;
57
91
  Swiss market practice requires a `Scope` and only `ZSR` sent one, so a bank-specific
@@ -63,11 +97,6 @@ rewrite of request building into builders/factories/handlers.
63
97
  keyring slots, so a `host_id` that no longer matched the stored prefix destroyed the
64
98
  bank's public keys on save. Unrecognised entries are now carried through unchanged,
65
99
  and a warning names each one instead of the failure being swallowed
66
- - [BREAKING] `order_id:` is validated against the EBICS `OrderIDType` range (`A000` to
67
- `ZZZZ`). Values below `A000` rendered as e.g. `0002`, which violates the
68
- `[A-Z][A-Z0-9]{3}` pattern; they now raise `ArgumentError`. The protocol form
69
- (`order_id: 'A000'`) is accepted alongside the integer
70
-
71
100
  - [FIX] `XCT` sent the wrong order type: `CD1` on H003/H004 and the DTAZV service on
72
101
  H005, filing credit transfers as direct debits. It now sends `XCT` on H003/H004.
73
102
  EBICS 3.0 defines no counterpart for XCT, so on H005 it raises
@@ -75,25 +104,16 @@ rewrite of request building into builders/factories/handlers.
75
104
  - [FIX] The electronic signature over order data is again formed with line separators
76
105
  removed, matching 2.x. The refactor had signed the raw document, changing the ES for
77
106
  every upload
78
- - [BREAKING] Upload order data is now normalized once, on the way in, so the bytes
79
- hashed for the electronic signature are the bytes transmitted. Until 3.0 the digest
80
- was taken over the normalized document while the raw one was sent, which only
81
- verifies against banks that normalize before checking the ES
82
107
  - [FIX] H005 `DataDigest` carried an RSA-PSS signature (256 bytes, non-reproducible)
83
108
  instead of the SHA-256 hash of the order data
84
109
  - [FIX] `Response#business_error?` treated every `01`-prefixed return code as
85
110
  informational, reporting `011001` (negative acknowledgement) and `011101` (segment
86
111
  number not reached) as success. It now uses an explicit allow-list
87
- - [BREAKING] Passing only one date-range bound (e.g. `client.STA(from)`) silently
88
- omitted the range and fetched the bank's full retention window. Both bounds are
89
- mandatory within `DateRange`, so this now raises `ArgumentError`
90
112
  - [FIX] `Time` and `DateTime` passed as date-range bounds rendered an `xs:dateTime`
91
113
  with a local offset into an `xs:date` element; they are now coerced to a date
92
114
  - [FIX] `C52` requested no container on H005 while `Client#C52` always unzips the
93
115
  response, raising `Zip::Error`. `C52` and `C53` now default to a ZIP container and
94
116
  both accept `container_type:` to override it
95
- - [BREAKING] Removed `Epics::Services::CryptService#sign`, which had no callers and
96
- disagreed with `#encrypt` on how an A005 signature is formed
97
117
  - [FIX] `Epics::Client.setup` forwarded its block to `.new`, which yields before the
98
118
  keys are generated, so the block saw an empty keyring and anything touching it —
99
119
  `ini_letter`, `save_keys`, `INI`, `HIA` — failed. It now yields once the client is
data/README.md CHANGED
@@ -307,6 +307,28 @@ practice requires it to be supplied (guidelines §5.2).
307
307
  When it is absent the bank assumes a global definition.
308
308
  On EBICS 2.4/2.5 these parameters have no equivalent and are ignored.
309
309
 
310
+ ### Signatures on uploads
311
+
312
+ EBICS 3.0 replaces the order attribute (`OZHNN`, `DZHNN`) with `SignatureFlag`, which tells
313
+ the bank whether an upload carries the user's ES (electronic signature). Uploads always
314
+ include it and send `SignatureFlag` by default, the equivalent of `OZHNN`: the bank expects
315
+ every required signature in the request and rejects the order otherwise.
316
+
317
+ ```ruby
318
+ # spool into the EDS (distributed electronic signature, German VEU) when signatures are
319
+ # still missing, e.g. for a user who needs a second signer
320
+ e.CCT(document, request_eds: true)
321
+
322
+ # no SignatureFlag: the order is authorised outside EBICS, e.g. by an accompanying slip
323
+ # or in online banking
324
+ e.CCT(document, signature_flag: false)
325
+ ```
326
+
327
+ `signature_flag: false` only drops the flag; the user's signature is still sent.
328
+
329
+ On EBICS 2.4/2.5 both options have no effect and are silently ignored: uploads keep
330
+ their fixed order attribute (`OZHNN` for the payment uploads).
331
+
310
332
  ## Features
311
333
 
312
334
  ### Initialization
@@ -32,8 +32,22 @@ class Epics::Builders::OrderDetailsBuilder::V3 < Epics::Builders::OrderDetailsBu
32
32
  end
33
33
 
34
34
  def add_btu_order_params(
35
- filename:, service_name:, msg_name:, scope: nil, service_option: nil, container_type: nil, msg_name_version: nil, msg_name_variant: nil, msg_name_format: nil
35
+ filename:,
36
+ service_name:,
37
+ msg_name:,
38
+ scope: nil,
39
+ service_option: nil,
40
+ container_type: nil,
41
+ msg_name_version: nil,
42
+ msg_name_variant: nil,
43
+ msg_name_format: nil,
44
+ signature_flag: true,
45
+ request_eds: false
36
46
  )
47
+ if request_eds && !signature_flag
48
+ raise ArgumentError, 'request_eds requires signature_flag'
49
+ end
50
+
37
51
  @xml.BTUOrderParams fileName: filename do |xml|
38
52
  xml.Service do
39
53
  xml.ServiceName service_name
@@ -46,6 +60,16 @@ class Epics::Builders::OrderDetailsBuilder::V3 < Epics::Builders::OrderDetailsBu
46
60
  msg_name_attributes[:format] = msg_name_format if msg_name_format
47
61
  xml.MsgName msg_name, msg_name_attributes
48
62
  end
63
+ # Without SignatureFlag the bank treats the order as carrying no ES (electronic
64
+ # signature) and expects authorisation outside EBICS. requestEDS asks the bank to
65
+ # spool the order into the EDS (distributed electronic signature, German VEU,
66
+ # "Verteilte Elektronische Unterschrift") instead of rejecting it when signatures
67
+ # are still missing.
68
+ if signature_flag
69
+ signature_flag_attributes = {}
70
+ signature_flag_attributes[:requestEDS] = 'true' if request_eds
71
+ xml.SignatureFlag '', signature_flag_attributes
72
+ end
49
73
  end
50
74
  end
51
75
  end
data/lib/epics/client.rb CHANGED
@@ -724,7 +724,7 @@ class Epics::Client
724
724
  data["#{host_id.upcase}.#{sig.version}"] = dump_signature(sig)
725
725
  end
726
726
 
727
- JSON.pretty_generate(data, JSON.dump_default_options)
727
+ JSON.pretty_generate(data)
728
728
  end
729
729
 
730
730
  # Keys without a certificate keep the bare-string form so key files stay readable by
@@ -3,21 +3,17 @@ class Epics::GenericUploadRequest < Epics::GenericRequest
3
3
 
4
4
  def initialize(client, document, **options)
5
5
  super(client, **options)
6
- self.document = document
6
+ @document = document
7
7
  aes_factory = Epics::Factories::Crypt::AesFactory.new
8
8
  aes = aes_factory.create
9
9
  @transaction_key = aes.key
10
10
  @crypt_service = Epics::Services::CryptService.new
11
11
  end
12
12
 
13
- # Normalized on the way in so the bytes hashed for the electronic signature are the
14
- # bytes transmitted.
15
- def document=(value)
16
- @document = value.gsub(/\n|\r/, '')
17
- end
18
-
13
+ # The ES hash excludes CR and LF; the bank strips them before verifying. The order
14
+ # data itself is transmitted unchanged.
19
15
  def document_digest
20
- @crypt_service.hash(document)
16
+ @crypt_service.hash(document.gsub(/\n|\r/, ''))
21
17
  end
22
18
 
23
19
  def to_transfer_xml
data/lib/epics/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Epics
4
4
 
5
- VERSION = '3.0.0.rc2'
5
+ VERSION = '3.0.1'
6
6
 
7
7
  end
@@ -317,6 +317,60 @@ RSpec.describe 'Builders' do
317
317
  expect(container).not_to be_nil
318
318
  expect(container['containerType']).to eq('ZIP')
319
319
  end
320
+
321
+ it 'adds SignatureFlag after Service by default' do
322
+ instance = described_class::V3.new do |b|
323
+ b.add_btu_order_params(
324
+ filename: 'test.xml', service_name: 'MCT', msg_name: 'pain.001'
325
+ )
326
+ end
327
+ xml = parse_doc(instance)
328
+ params = xml.at('OrderDetails/BTUOrderParams')
329
+ expect(params.element_children.map(&:name)).to eq(%w[Service SignatureFlag])
330
+ expect(params.at('SignatureFlag')['requestEDS']).to be_nil
331
+ end
332
+
333
+ it 'sets requestEDS on SignatureFlag when request_eds is given' do
334
+ instance = described_class::V3.new do |b|
335
+ b.add_btu_order_params(
336
+ filename: 'test.xml',
337
+ service_name: 'MCT',
338
+ msg_name: 'pain.001',
339
+ request_eds: true
340
+ )
341
+ end
342
+ xml = parse_doc(instance)
343
+ params = xml.at('OrderDetails/BTUOrderParams')
344
+ expect(params.at('SignatureFlag')['requestEDS']).to eq('true')
345
+ end
346
+
347
+ it 'omits SignatureFlag when signature_flag is false' do
348
+ instance = described_class::V3.new do |b|
349
+ b.add_btu_order_params(
350
+ filename: 'test.xml',
351
+ service_name: 'MCT',
352
+ msg_name: 'pain.001',
353
+ signature_flag: false
354
+ )
355
+ end
356
+ xml = parse_doc(instance)
357
+ params = xml.at('OrderDetails/BTUOrderParams')
358
+ expect(params.at('SignatureFlag')).to be_nil
359
+ end
360
+
361
+ it 'rejects request_eds without signature_flag' do
362
+ expect do
363
+ described_class::V3.new do |b|
364
+ b.add_btu_order_params(
365
+ filename: 'test.xml',
366
+ service_name: 'MCT',
367
+ msg_name: 'pain.001',
368
+ signature_flag: false,
369
+ request_eds: true
370
+ )
371
+ end
372
+ end.to raise_error(ArgumentError, /request_eds/)
373
+ end
320
374
  end
321
375
  end
322
376
  end
@@ -439,6 +439,25 @@ RSpec.describe 'RequestFactory' do
439
439
  params = xml.at('//BTUOrderParams')
440
440
  expect(params['fileName']).to eq('custom.xml')
441
441
  end
442
+
443
+ it 'create_xe2 without override has SignatureFlag without requestEDS' do
444
+ xml = parse(factory.create_xe2(digest, transaction_key))
445
+ params = xml.at('//BTUOrderParams')
446
+ expect(params.at('SignatureFlag')).not_to be_nil
447
+ expect(params.at('SignatureFlag')['requestEDS']).to be_nil
448
+ end
449
+
450
+ it 'create_xe2 accepts request_eds override' do
451
+ xml = parse(factory.create_xe2(digest, transaction_key, request_eds: true))
452
+ params = xml.at('//BTUOrderParams')
453
+ expect(params.at('SignatureFlag')['requestEDS']).to eq('true')
454
+ end
455
+
456
+ it 'create_xe2 accepts signature_flag override' do
457
+ xml = parse(factory.create_xe2(digest, transaction_key, signature_flag: false))
458
+ params = xml.at('//BTUOrderParams')
459
+ expect(params.at('SignatureFlag')).to be_nil
460
+ end
442
461
  end
443
462
  end
444
463
  end
@@ -469,6 +469,10 @@ RSpec.shared_examples 'a valid ebicsRequest H005 upload' do |service_name:, msg_
469
469
  expect(service.at_xpath('e:MsgName', ns).text).to eq(msg_name)
470
470
  end
471
471
 
472
+ it 'has SignatureFlag' do
473
+ expect(xml.at_xpath('//e:header/e:static/e:OrderDetails/e:BTUOrderParams/e:SignatureFlag', ns)).not_to be_nil
474
+ end
475
+
472
476
  if scope
473
477
  it "has Scope #{scope}" do
474
478
  service = xml.at_xpath('//e:header/e:static/e:OrderDetails/e:BTUOrderParams/e:Service', ns)
@@ -22,11 +22,18 @@ RSpec.describe 'upload order data digests' do
22
22
  .not_to eq(OpenSSL::Digest.digest('sha256', document))
23
23
  end
24
24
 
25
- # Hashed bytes must equal transmitted bytes.
26
- it 'normalizes once, so the digest covers exactly what is transmitted' do
27
- expect(subject.document).not_to include("\n")
28
- expect(subject.document_digest)
29
- .to eq(OpenSSL::Digest.digest('sha256', subject.document))
25
+ # Callers may hash the content themselves, e.g. a container's HashValue over the
26
+ # canonical message, so the transmitted order data must be the input as given.
27
+ it 'transmits the document unchanged' do
28
+ subject.transaction_id = 'ABC'
29
+ order_data = Nokogiri::XML(subject.to_transfer_xml)
30
+ .at_xpath('//e:OrderData', 'e' => 'urn:org:ebics:H004').text
31
+ compressed = Epics::Services::CryptService
32
+ .new
33
+ .decrypt_by_key(subject.transaction_key, Base64.decode64(order_data))
34
+
35
+ expect(document).to include("\n")
36
+ expect(Zlib::Inflate.inflate(compressed)).to eq(document)
30
37
  end
31
38
 
32
39
  it 'is identical for CRLF and LF encodings of the same document' do
@@ -56,6 +63,18 @@ RSpec.describe 'upload order data digests' do
56
63
  .to eq(data_digest_of(order).text)
57
64
  end
58
65
 
66
+ it 'transmits the document unchanged' do
67
+ order = Epics::CCT.new(client, document)
68
+ order.transaction_id = 'ABC'
69
+ order_data = Nokogiri::XML(order.to_transfer_xml).at_xpath('//e:OrderData', ns).text
70
+ compressed = Epics::Services::CryptService
71
+ .new
72
+ .decrypt_by_key(order.transaction_key, Base64.decode64(order_data))
73
+
74
+ expect(document).to include("\n")
75
+ expect(Zlib::Inflate.inflate(compressed)).to eq(document)
76
+ end
77
+
59
78
  it 'is reproducible across two builds of the same document' do
60
79
  expect(data_digest_of(Epics::CCT.new(client, document)).text)
61
80
  .to eq(data_digest_of(Epics::CCT.new(client, document)).text)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epics
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.rc2
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Brillert