epics 2.11.0 → 3.0.0.rc1
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 +4 -4
- data/.gitignore +2 -1
- data/.semaphore/semaphore.yml +17 -8
- data/CHANGELOG.md +93 -0
- data/EBICS_REFERENCES.md +169 -0
- data/README.md +219 -50
- data/lib/epics/azv.rb +3 -10
- data/lib/epics/b2b.rb +3 -10
- data/lib/epics/bka.rb +3 -14
- data/lib/epics/builders/body_builder/base.rb +26 -0
- data/lib/epics/builders/body_builder/v2.rb +9 -0
- data/lib/epics/builders/body_builder/v3.rb +9 -0
- data/lib/epics/builders/body_builder.rb +6 -0
- data/lib/epics/builders/data_encryption_info_builder.rb +29 -0
- data/lib/epics/builders/data_transfer_builder/base.rb +52 -0
- data/lib/epics/builders/data_transfer_builder/v2.rb +9 -0
- data/lib/epics/builders/data_transfer_builder/v3.rb +12 -0
- data/lib/epics/builders/data_transfer_builder.rb +6 -0
- data/lib/epics/builders/header_builder/base.rb +26 -0
- data/lib/epics/builders/header_builder/v2.rb +9 -0
- data/lib/epics/builders/header_builder/v3.rb +9 -0
- data/lib/epics/builders/header_builder.rb +6 -0
- data/lib/epics/builders/mutable_builder.rb +28 -0
- data/lib/epics/builders/order_details_builder/base.rb +100 -0
- data/lib/epics/builders/order_details_builder/v2.rb +21 -0
- data/lib/epics/builders/order_details_builder/v3.rb +51 -0
- data/lib/epics/builders/order_details_builder.rb +10 -0
- data/lib/epics/builders/request_builder.rb +25 -0
- data/lib/epics/builders/static_builder/base.rb +75 -0
- data/lib/epics/builders/static_builder/v2.rb +9 -0
- data/lib/epics/builders/static_builder/v3.rb +9 -0
- data/lib/epics/builders/static_builder.rb +9 -0
- data/lib/epics/builders/transfer_receipt_builder.rb +19 -0
- data/lib/epics/builders/xml_builder/base.rb +92 -0
- data/lib/epics/builders/xml_builder/v24.rb +31 -0
- data/lib/epics/builders/xml_builder/v25.rb +31 -0
- data/lib/epics/builders/xml_builder/v3.rb +31 -0
- data/lib/epics/builders/xml_builder.rb +7 -0
- data/lib/epics/builders.rb +13 -0
- data/lib/epics/c2s.rb +3 -10
- data/lib/epics/c52.rb +2 -14
- data/lib/epics/c53.rb +2 -14
- data/lib/epics/c54.rb +2 -14
- data/lib/epics/c5n.rb +3 -14
- data/lib/epics/ccs.rb +3 -10
- data/lib/epics/cct.rb +3 -10
- data/lib/epics/cd1.rb +3 -10
- data/lib/epics/cdb.rb +3 -10
- data/lib/epics/cdd.rb +3 -10
- data/lib/epics/cds.rb +3 -10
- data/lib/epics/cdz.rb +3 -14
- data/lib/epics/cip.rb +3 -10
- data/lib/epics/client.rb +489 -125
- data/lib/epics/crypt/aes.rb +56 -0
- data/lib/epics/crypt/x_509.rb +52 -0
- data/lib/epics/crypt.rb +5 -0
- data/lib/epics/crz.rb +3 -14
- data/lib/epics/factories/crypt/aes_factory.rb +5 -0
- data/lib/epics/factories/crypt.rb +4 -0
- data/lib/epics/factories/request_factory/base.rb +236 -0
- data/lib/epics/factories/request_factory/v2.rb +194 -0
- data/lib/epics/factories/request_factory/v24.rb +32 -0
- data/lib/epics/factories/request_factory/v25.rb +31 -0
- data/lib/epics/factories/request_factory/v3.rb +305 -0
- data/lib/epics/factories/request_factory.rb +8 -0
- data/lib/epics/factories.rb +5 -0
- data/lib/epics/fdl.rb +3 -18
- data/lib/epics/ful.rb +3 -15
- data/lib/epics/generic_request.rb +31 -92
- data/lib/epics/generic_upload_request.rb +14 -67
- data/lib/epics/haa.rb +3 -9
- data/lib/epics/hac.rb +3 -14
- data/lib/epics/handlers/auth_signature_handler.rb +102 -0
- data/lib/epics/handlers/order_data_handler/base.rb +98 -0
- data/lib/epics/handlers/order_data_handler/v2.rb +39 -0
- data/lib/epics/handlers/order_data_handler/v24.rb +11 -0
- data/lib/epics/handlers/order_data_handler/v25.rb +11 -0
- data/lib/epics/handlers/order_data_handler/v3.rb +41 -0
- data/lib/epics/handlers/order_data_handler.rb +8 -0
- data/lib/epics/handlers/user_signature_handler/base.rb +10 -0
- data/lib/epics/handlers/user_signature_handler/v2.rb +17 -0
- data/lib/epics/handlers/user_signature_handler/v3.rb +17 -0
- data/lib/epics/handlers/user_signature_handler.rb +6 -0
- data/lib/epics/handlers.rb +6 -0
- data/lib/epics/hev.rb +2 -15
- data/lib/epics/hia.rb +4 -55
- data/lib/epics/hkd.rb +3 -9
- data/lib/epics/hpb.rb +3 -13
- data/lib/epics/hpd.rb +3 -9
- data/lib/epics/htd.rb +3 -9
- data/lib/epics/ini.rb +4 -46
- data/lib/epics/keyring.rb +17 -0
- data/lib/epics/letter_renderer.rb +17 -22
- data/lib/epics/ptk.rb +3 -14
- data/lib/epics/response.rb +34 -29
- data/lib/epics/services/crypt_service.rb +62 -0
- data/lib/epics/services/digest_resolver/base.rb +30 -0
- data/lib/epics/services/digest_resolver/v2.rb +13 -0
- data/lib/epics/services/digest_resolver/v3.rb +19 -0
- data/lib/epics/services/digest_resolver.rb +6 -0
- data/lib/epics/services.rb +5 -0
- data/lib/epics/signature.rb +50 -0
- data/lib/epics/{key.rb → signature_algorithm/base.rb} +14 -21
- data/lib/epics/signature_algorithm/rsa.rb +50 -0
- data/lib/epics/signature_algorithm/rsapkcs1.rb +16 -0
- data/lib/epics/signature_algorithm/rsapss.rb +20 -0
- data/lib/epics/signature_algorithm.rb +2 -0
- data/lib/epics/sta.rb +3 -14
- data/lib/epics/version.rb +1 -1
- data/lib/epics/vmk.rb +3 -14
- data/lib/epics/wss.rb +3 -9
- data/lib/epics/xct.rb +3 -10
- data/lib/epics/xds.rb +3 -10
- data/lib/epics/xe2.rb +3 -10
- data/lib/epics/xe3.rb +3 -10
- data/lib/epics/xek.rb +5 -0
- data/lib/epics/yct.rb +6 -0
- data/lib/epics/z01.rb +2 -14
- data/lib/epics/z52.rb +2 -14
- data/lib/epics/z53.rb +2 -14
- data/lib/epics/z54.rb +2 -14
- data/lib/epics/zsr.rb +5 -0
- data/lib/epics.rb +68 -5
- data/lib/letter/ini.erb +27 -6
- data/spec/aes_spec.rb +46 -0
- data/spec/auth_signature_handler_spec.rb +58 -0
- data/spec/builders_spec.rb +616 -0
- data/spec/certificate_handling_spec.rb +189 -0
- data/spec/client_setup_block_spec.rb +41 -0
- data/spec/client_spec.rb +143 -91
- data/spec/crypt_service_spec.rb +132 -0
- data/spec/date_range_spec.rb +46 -0
- data/spec/digest_resolver_spec.rb +164 -0
- data/spec/fixtures/bank_x509.pem +3 -0
- data/spec/fixtures/xml/haa.xml +40 -1
- data/spec/fixtures/xml/haa_receipt.xml +33 -30
- data/spec/fixtures/xml/haa_response_h004.xml +4 -0
- data/spec/fixtures/xml/haa_response_h005.xml +38 -0
- data/spec/fixtures/xml/hia_request_order_data.xml +24 -1
- data/spec/fixtures/xml/hpb_request.xml +1 -1
- data/spec/fixtures/xml/hpb_response_x509.xml +16 -0
- data/spec/fixtures/xml/htd_order_data_h005.xml +55 -0
- data/spec/fixtures/xml/jruby/hia.xml +20 -22
- data/spec/fixtures/xml/jruby/ini.xml +1 -3
- data/spec/fixtures/xml/ruby/hia.xml +21 -1
- data/spec/fixtures/xml/ruby/ini.xml +21 -1
- data/spec/handlers_spec.rb +267 -0
- data/spec/hpb_malformed_certificate_spec.rb +36 -0
- data/spec/key_file_decryption_spec.rb +27 -0
- data/spec/key_file_preservation_spec.rb +78 -0
- data/spec/keyring_spec.rb +349 -0
- data/spec/middleware/parse_ebics_spec.rb +2 -1
- data/spec/order_id_spec.rb +65 -0
- data/spec/orders/azv_spec.rb +63 -7
- data/spec/orders/b2b_spec.rb +68 -3
- data/spec/orders/bka_spec.rb +34 -4
- data/spec/orders/c2s_spec.rb +62 -9
- data/spec/orders/c52_spec.rb +34 -4
- data/spec/orders/c53_spec.rb +34 -4
- data/spec/orders/c54_spec.rb +36 -4
- data/spec/orders/c5n_spec.rb +36 -4
- data/spec/orders/camt_parity_spec.rb +80 -0
- data/spec/orders/ccs_spec.rb +62 -9
- data/spec/orders/cct_spec.rb +83 -9
- data/spec/orders/cd1_spec.rb +50 -7
- data/spec/orders/cdb_spec.rb +64 -7
- data/spec/orders/cdd_spec.rb +64 -7
- data/spec/orders/cds_spec.rb +74 -0
- data/spec/orders/cdz_spec.rb +67 -4
- data/spec/orders/cip_spec.rb +61 -9
- data/spec/orders/crz_spec.rb +67 -4
- data/spec/orders/fdl_spec.rb +143 -13
- data/spec/orders/ful_spec.rb +51 -10
- data/spec/orders/haa_spec.rb +115 -9
- data/spec/orders/hac_spec.rb +89 -10
- data/spec/orders/hev_spec.rb +1 -6
- data/spec/orders/hia_spec.rb +25 -22
- data/spec/orders/hkd_spec.rb +23 -4
- data/spec/orders/hpb_spec.rb +7 -4
- data/spec/orders/hpd_spec.rb +23 -4
- data/spec/orders/htd_spec.rb +23 -4
- data/spec/orders/ini_spec.rb +23 -18
- data/spec/orders/ptk_spec.rb +27 -4
- data/spec/orders/sta_spec.rb +129 -11
- data/spec/orders/vmk_spec.rb +36 -4
- data/spec/orders/wss_spec.rb +29 -3
- data/spec/orders/x_509_certificate_spec.rb +9 -1
- data/spec/orders/xct_spec.rb +63 -0
- data/spec/orders/xds_spec.rb +13 -6
- data/spec/orders/xe2_spec.rb +64 -7
- data/spec/orders/xe3_spec.rb +64 -7
- data/spec/orders/xek_spec.rb +31 -0
- data/spec/orders/yct_spec.rb +43 -0
- data/spec/orders/z01_spec.rb +34 -4
- data/spec/orders/z52_spec.rb +34 -4
- data/spec/orders/z53_spec.rb +34 -4
- data/spec/orders/z54_spec.rb +36 -4
- data/spec/orders/zsr_spec.rb +31 -0
- data/spec/request_factory_spec.rb +444 -0
- data/spec/response_business_error_spec.rb +45 -0
- data/spec/response_validation_spec.rb +23 -0
- data/spec/{key_spec.rb → signature_algorithm/rsa_pss_spec.rb} +4 -13
- data/spec/support/ebics_matcher.rb +17 -9
- data/spec/support/ebics_shared_examples.rb +642 -0
- data/spec/support/h005_client_helper.rb +23 -0
- data/spec/upload_digest_spec.rb +69 -0
- data/spec/user_signature_handler_spec.rb +25 -0
- data/spec/version_support_error_spec.rb +76 -0
- data/spec/xsd/{ebics_hev.xsd → H000/ebics_hev.xsd} +0 -0
- data/spec/xsd/H003/ebics.xsd +11 -0
- data/spec/xsd/H003/ebics_keymgmt_request.xsd +555 -0
- data/spec/xsd/H003/ebics_keymgmt_response.xsd +131 -0
- data/spec/xsd/H003/ebics_orders.xsd +1811 -0
- data/spec/xsd/H003/ebics_request.xsd +354 -0
- data/spec/xsd/H003/ebics_response.xsd +161 -0
- data/spec/xsd/{ebics_signature.xsd → H003/ebics_signature.xsd} +0 -0
- data/spec/xsd/H003/ebics_types.xsd +2361 -0
- data/spec/xsd/H003/xmldsig-core-schema.xsd +318 -0
- data/spec/xsd/H004/ebics_signature.xsd +217 -0
- data/spec/xsd/H005/ebics_H005.xsd +11 -0
- data/spec/xsd/H005/ebics_keymgmt_request_H005.xsd +523 -0
- data/spec/xsd/H005/ebics_keymgmt_response_H005.xsd +137 -0
- data/spec/xsd/H005/ebics_orders_H005.xsd +2094 -0
- data/spec/xsd/H005/ebics_request_H005.xsd +349 -0
- data/spec/xsd/H005/ebics_response_H005.xsd +167 -0
- data/spec/xsd/H005/ebics_signature_S002.xsd +177 -0
- data/spec/xsd/H005/ebics_types_H005.xsd +1885 -0
- data/spec/xsd/H005/xmldsig-core-schema.xsd +318 -0
- metadata +197 -36
- data/lib/epics/header_request.rb +0 -58
- data/lib/epics/middleware/xmlsig.rb +0 -16
- data/lib/epics/signer.rb +0 -40
- data/lib/epics/x_509_certificate.rb +0 -15
- data/lib/letter/ini_with_certs.erb +0 -335
- data/spec/generic_upload_spec.rb +0 -58
- data/spec/hpb_spec.rb +0 -15
- data/spec/signer_spec.rb +0 -34
- /data/spec/xsd/{ebics_H004.xsd → H004/ebics_H004.xsd} +0 -0
- /data/spec/xsd/{ebics_keymgmt_request_H004.xsd → H004/ebics_keymgmt_request_H004.xsd} +0 -0
- /data/spec/xsd/{ebics_keymgmt_response_H004.xsd → H004/ebics_keymgmt_response_H004.xsd} +0 -0
- /data/spec/xsd/{ebics_orders_H004.xsd → H004/ebics_orders_H004.xsd} +0 -0
- /data/spec/xsd/{ebics_request_H004.xsd → H004/ebics_request_H004.xsd} +0 -0
- /data/spec/xsd/{ebics_response_H004.xsd → H004/ebics_response_H004.xsd} +0 -0
- /data/spec/xsd/{ebics_types_H004.xsd → H004/ebics_types_H004.xsd} +0 -0
- /data/spec/xsd/{xmldsig-core-schema.xsd → H004/xmldsig-core-schema.xsd} +0 -0
data/README.md
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
EPICS is a ruby implementation of the [EBICS](https://www.ebics.org/) (Electronic Banking Internet
|
|
7
7
|
Communication Standard).
|
|
8
8
|
|
|
9
|
-
It supports EBICS 2.5.
|
|
9
|
+
It supports EBICS 2.4 (H003), 2.5 (H004) and 3.0 (H005). The default is 2.5 — see
|
|
10
|
+
[EBICS 3.0 (H005)](#ebics-30-h005) for what differs on 3.0.
|
|
10
11
|
|
|
11
12
|
The client supports the complete initialization process comprising INI, HIA and HPB including the
|
|
12
13
|
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 FUL)
|
|
14
|
+
(STA HAA HTD HPD PTK HAC HKD BKA C52 C53 C54 CD1 CDB CDD CCT VMK FDL FUL), and the H005-only
|
|
15
|
+
types XEK, ZSR, and YCT.
|
|
14
16
|
|
|
15
17
|
## Installation
|
|
16
18
|
|
|
@@ -41,7 +43,20 @@ Once the paperwork is done, your bank should provide you with:
|
|
|
41
43
|
Take these parameters and start setting up an UserID (repeat this for every user you want to initialize):
|
|
42
44
|
|
|
43
45
|
```ruby
|
|
44
|
-
e = Epics::Client.setup("my-super-secret", "https://ebics.sandbox", "EBICS_HOST_ID", "EBICS_USER_ID", "EBICS_PARTNER_ID"
|
|
46
|
+
e = Epics::Client.setup("my-super-secret", "https://ebics.sandbox", "EBICS_HOST_ID", "EBICS_USER_ID", "EBICS_PARTNER_ID")
|
|
47
|
+
|
|
48
|
+
# the key size defaults to 4096 bits - pass a different one as the sixth argument
|
|
49
|
+
e = Epics::Client.setup("my-super-secret", "https://ebics.sandbox", "EBICS_HOST_ID", "EBICS_USER_ID", "EBICS_PARTNER_ID", 2048)
|
|
50
|
+
|
|
51
|
+
# for EBICS 3.0, ask for H005 — this also generates the X.509 certificates it requires
|
|
52
|
+
e = Epics::Client.setup(
|
|
53
|
+
"my-super-secret",
|
|
54
|
+
"https://ebics.sandbox",
|
|
55
|
+
"EBICS_HOST_ID",
|
|
56
|
+
"EBICS_USER_ID",
|
|
57
|
+
"EBICS_PARTNER_ID",
|
|
58
|
+
version: Epics::Keyring::VERSION_30,
|
|
59
|
+
)
|
|
45
60
|
```
|
|
46
61
|
|
|
47
62
|
To use the keys later, just store them in a file
|
|
@@ -68,7 +83,7 @@ e.save_ini_letter( 'My Banks Name', "/home/epics/ini.html" )
|
|
|
68
83
|
```
|
|
69
84
|
|
|
70
85
|
Open the generated HTML file in your favorite browser and print it (skipping
|
|
71
|
-
header and footer sounds like a solid setting here
|
|
86
|
+
header and footer sounds like a solid setting here). In case you're having difficulties
|
|
72
87
|
with the encoding, try forcing your browser to use UTF-8.
|
|
73
88
|
|
|
74
89
|
Put the INI letter in an envelope and mail it to your bank!
|
|
@@ -124,6 +139,174 @@ You can choose to configure some default values like this
|
|
|
124
139
|
e = Epics::Client.new(keys, 'passphrase', 'url', 'host', 'user', 'partner', locale: :fr, product_name: 'Mon Epic Client EBICS')
|
|
125
140
|
```
|
|
126
141
|
|
|
142
|
+
### Order IDs on EBICS 2.4
|
|
143
|
+
|
|
144
|
+
Only EBICS 2.4 numbers its own orders. 2.5 and 3.0 leave that to the bank. The counter runs
|
|
145
|
+
from `A000` to `ZZZZ` and is **not** stored in the key file, so a process that restarts
|
|
146
|
+
begins at `A000` again and re-sends order IDs the bank has already seen. Persist it
|
|
147
|
+
yourself and hand it back:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
e = Epics::Client.new(
|
|
151
|
+
keys,
|
|
152
|
+
'passphrase',
|
|
153
|
+
'url',
|
|
154
|
+
'host',
|
|
155
|
+
'user',
|
|
156
|
+
'partner',
|
|
157
|
+
version: Epics::Keyring::VERSION_24,
|
|
158
|
+
order_id: last_used_order_id,
|
|
159
|
+
) # 'A0FC' or the integer it encodes
|
|
160
|
+
|
|
161
|
+
e.CD1(document)
|
|
162
|
+
store(e.current_order_id)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## EBICS 3.0 (H005)
|
|
166
|
+
|
|
167
|
+
### Choosing the protocol version
|
|
168
|
+
|
|
169
|
+
Pass `version:` to `Client.new` and `Client.setup`. It defaults to 2.5.
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
Epics::Keyring::VERSION_24 # => "H003", EBICS 2.4
|
|
173
|
+
Epics::Keyring::VERSION_25 # => "H004", EBICS 2.5 (default)
|
|
174
|
+
Epics::Keyring::VERSION_30 # => "H005", EBICS 3.0
|
|
175
|
+
|
|
176
|
+
e = Epics::Client.new(
|
|
177
|
+
keys,
|
|
178
|
+
'passphrase',
|
|
179
|
+
'url',
|
|
180
|
+
'host',
|
|
181
|
+
'user',
|
|
182
|
+
'partner',
|
|
183
|
+
version: Epics::Keyring::VERSION_30,
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Certificates are mandatory
|
|
188
|
+
|
|
189
|
+
EBICS 3.0 carries the public keys inside `ds:X509Data`: there is no modulus/exponent form
|
|
190
|
+
as in 2.x, and the specification requires X.509 data in the key management order types
|
|
191
|
+
(EBICS 3.0.2 §3.9). An H005 client without certificates cannot run INI or HIA and raises
|
|
192
|
+
`Epics::MissingCertificateError`.
|
|
193
|
+
|
|
194
|
+
`Client.setup` therefore generates a self-signed certificate for each key when the version
|
|
195
|
+
is H005. Self-signed certificates are explicitly permitted for INI/HIA by the Swiss market
|
|
196
|
+
practice guidelines (§6.1). Confirm what your bank expects before relying on them.
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
e = Epics::Client.setup(
|
|
200
|
+
'passphrase',
|
|
201
|
+
'url',
|
|
202
|
+
'host',
|
|
203
|
+
'user',
|
|
204
|
+
'partner',
|
|
205
|
+
version: Epics::Keyring::VERSION_30,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
e.INI # sends the signature key, as a certificate
|
|
209
|
+
e.HIA # sends the authentication and encryption keys, as certificates
|
|
210
|
+
e.save_ini_letter('My Banks Name', '/home/epics/ini.html')
|
|
211
|
+
e.save_keys('/home/epics/my.key')
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The subject defaults to the client's own identity (`CN=<user>`, `OU=<partner>`, `O=<host>`)
|
|
215
|
+
and the certificate does not expire. Both are configurable, as is turning generation off:
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
Epics::Client.setup(
|
|
219
|
+
'passphrase',
|
|
220
|
+
'url',
|
|
221
|
+
'host',
|
|
222
|
+
'user',
|
|
223
|
+
'partner',
|
|
224
|
+
version: Epics::Keyring::VERSION_30,
|
|
225
|
+
certificate_subject: '/CN=my.example.org/O=My Company/C=CH',
|
|
226
|
+
certificate_not_after: Time.utc(2030, 1, 1),
|
|
227
|
+
generate_certificates: false,
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Supplying your own certificates
|
|
232
|
+
|
|
233
|
+
Banks that require CA-issued certificates take precedence over generation — pass the PEM
|
|
234
|
+
content and it is used as-is:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
e = Epics::Client.new(
|
|
238
|
+
keys,
|
|
239
|
+
'passphrase',
|
|
240
|
+
'url',
|
|
241
|
+
'host',
|
|
242
|
+
'user',
|
|
243
|
+
'partner',
|
|
244
|
+
version: Epics::Keyring::VERSION_30,
|
|
245
|
+
x_509_certificate_a_content: File.read('cert_a.pem'),
|
|
246
|
+
x_509_certificate_x_content: File.read('cert_x.pem'),
|
|
247
|
+
x_509_certificate_e_content: File.read('cert_e.pem'),
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Certificates are stored in the key file alongside their key, so they are restored by
|
|
252
|
+
`Client.new` without passing the options again. Content that cannot be parsed raises
|
|
253
|
+
`Epics::InvalidCertificateError` naming the option it came from.
|
|
254
|
+
|
|
255
|
+
### Signature version
|
|
256
|
+
|
|
257
|
+
`Client.setup` uses A006 (RSASSA-PSS). Banks that still require A005 (RSASSA-PKCS1-v1_5)
|
|
258
|
+
can ask for it:
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
Epics::Client.setup(
|
|
262
|
+
'passphrase',
|
|
263
|
+
'url',
|
|
264
|
+
'host',
|
|
265
|
+
'user',
|
|
266
|
+
'partner',
|
|
267
|
+
version: Epics::Keyring::VERSION_30,
|
|
268
|
+
signature_version: Epics::Signature::A_VERSION_5,
|
|
269
|
+
)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Bank keys
|
|
273
|
+
|
|
274
|
+
`HPB` reads the bank's keys from the certificates in its response and stores them with the
|
|
275
|
+
keyring, so `save_keys` keeps them. On H005 the key digests sent with each request are
|
|
276
|
+
SHA-256 fingerprints of those certificates rather than digests of the raw key.
|
|
277
|
+
|
|
278
|
+
### HAA and HTD return services, not order types
|
|
279
|
+
|
|
280
|
+
EBICS 3.0 replaces order types with BTF service parameters, so on H005 `HAA` and
|
|
281
|
+
`order_types` return an array of hashes instead of an array of strings:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
# H003/H004
|
|
285
|
+
e.order_types # => ["C53", "CCT", "STA", ...]
|
|
286
|
+
|
|
287
|
+
# H005
|
|
288
|
+
e.order_types # => [{ service_name: "EOP", scope: "CH", msg_name: "camt.053", container: "ZIP" },
|
|
289
|
+
# { service_name: "SCT", scope: "CH", msg_name: "pain.001" }, ...]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Code that does `order_types.include?('CCT')` will silently find nothing on H005.
|
|
293
|
+
|
|
294
|
+
### Service parameters
|
|
295
|
+
|
|
296
|
+
Download and upload order types send BTF parameters derived from the order type. Where a
|
|
297
|
+
bank differs from the default, override them per call:
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
e.C53(from, to, scope: 'CH', msg_name_version: '08', container_type: 'ZIP')
|
|
301
|
+
e.CCT(document, service_option: 'CH001COR')
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The `Scope` parameter identifies whose rulebook applies — an ISO country code such as `CH`
|
|
305
|
+
or `DE`, `GLB` for SEPA/SWIFT/CGI, or `BIL` for bilaterally agreed rules. Swiss market
|
|
306
|
+
practice requires it to be supplied (guidelines §5.2).
|
|
307
|
+
When it is absent the bank assumes a global definition.
|
|
308
|
+
On EBICS 2.4/2.5 these parameters have no equivalent and are ignored.
|
|
309
|
+
|
|
127
310
|
## Features
|
|
128
311
|
|
|
129
312
|
### Initialization
|
|
@@ -143,6 +326,8 @@ Currently this EPICS implementation supports the following order types:
|
|
|
143
326
|
- PTK (customer usage report in text format)
|
|
144
327
|
- HAC (customer usage report in xml format)
|
|
145
328
|
- VMK (customer usage report in xml format)
|
|
329
|
+
- XEK (account statements as PDF documents, H005 only)
|
|
330
|
+
- ZSR (payment status reports, H005 only)
|
|
146
331
|
- ... more coming soon
|
|
147
332
|
|
|
148
333
|
Example:
|
|
@@ -175,6 +360,7 @@ puts e.STA('2014-09-01', '2014-09-11')
|
|
|
175
360
|
- CDB (Uploads a SEPA Direct Debit document of type B2B)
|
|
176
361
|
- CDD (Uploads a SEPA Direct Debit document of type CORE)
|
|
177
362
|
- CCT (Uploads a SEPA Credit document)
|
|
363
|
+
- YCT (Uploads a multi-currency credit transfer, H005 only)
|
|
178
364
|
- ... more coming soon
|
|
179
365
|
|
|
180
366
|
Example:
|
|
@@ -202,52 +388,28 @@ about the supported functionalities.
|
|
|
202
388
|
|
|
203
389
|
### Using X.509 Certificates
|
|
204
390
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Some banks require X.509 certificates for EBICS initialization (INI/HIA).
|
|
391
|
+
Some banks require X.509 certificates for initialization (INI/HIA) even on EBICS 2.x,
|
|
392
|
+
where the classic key-based workflow is otherwise used. On EBICS 3.0 they are mandatory —
|
|
393
|
+
see [EBICS 3.0 (H005)](#ebics-30-h005).
|
|
210
394
|
|
|
211
|
-
|
|
395
|
+
`Epics::Crypt::X509.generate` builds a self-signed certificate for one of your keys:
|
|
212
396
|
|
|
213
|
-
This examples showcases the generation of the X.509 certificate A file and can be applied the same way for the others.
|
|
214
397
|
```ruby
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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)
|
|
398
|
+
certificate = Epics::Crypt::X509.generate(
|
|
399
|
+
client.signature_key, # or authentication_key / encryption_key
|
|
400
|
+
subject: '/CN=my.example.org/O=My Company/C=DE'
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
File.write('cert_a.pem', certificate.to_pem)
|
|
237
404
|
```
|
|
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
405
|
|
|
242
|
-
|
|
406
|
+
It does not expire unless you say so. Pass `not_before:` and `not_after:` for a bounded
|
|
407
|
+
validity. On H005, `Client.setup` calls this for all three keys already, so you only need
|
|
408
|
+
it directly when adding certificates to an existing 2.x client.
|
|
243
409
|
|
|
244
|
-
|
|
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")
|
|
410
|
+
Pass the PEM content when creating the client:
|
|
250
411
|
|
|
412
|
+
```ruby
|
|
251
413
|
client = Epics::Client.new(
|
|
252
414
|
keys, # your key data as before
|
|
253
415
|
'passphrase',
|
|
@@ -255,20 +417,27 @@ client = Epics::Client.new(
|
|
|
255
417
|
'host',
|
|
256
418
|
'user',
|
|
257
419
|
'partner',
|
|
258
|
-
x_509_certificate_a_content:
|
|
259
|
-
x_509_certificate_x_content:
|
|
260
|
-
x_509_certificate_e_content:
|
|
420
|
+
x_509_certificate_a_content: File.read('cert_a.pem'),
|
|
421
|
+
x_509_certificate_x_content: File.read('cert_x.pem'),
|
|
422
|
+
x_509_certificate_e_content: File.read('cert_e.pem'),
|
|
261
423
|
debug_mode: true # Optional: enables verbose logging of EBICS requests/responses
|
|
262
424
|
)
|
|
263
425
|
```
|
|
426
|
+
|
|
427
|
+
`save_keys` stores the certificates with the keys, so later `Client.new` calls restore
|
|
428
|
+
them without the options.
|
|
429
|
+
|
|
430
|
+
**Note:** For production environments, your bank may require certificates issued by a
|
|
431
|
+
trusted authority. Be sure to confirm your bank's requirements before proceeding.
|
|
432
|
+
|
|
264
433
|
### Example: Generating the Initialization Letter with Certificates
|
|
265
434
|
|
|
266
435
|
```ruby
|
|
267
|
-
|
|
268
|
-
letter = renderer.render("Your Bank Name")
|
|
269
|
-
File.write("initialization_letter.txt", letter)
|
|
436
|
+
client.save_ini_letter('Your Bank Name', 'initialization_letter.html')
|
|
270
437
|
```
|
|
271
|
-
|
|
438
|
+
|
|
439
|
+
Where a key has a certificate, the letter prints the SHA-256 hash of that certificate as
|
|
440
|
+
required for certificate-based registration, rather than the hash of the raw key.
|
|
272
441
|
|
|
273
442
|
## Issues and Feature Requests
|
|
274
443
|
|
data/lib/epics/azv.rb
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
class Epics::AZV < Epics::GenericUploadRequest
|
|
2
|
-
def
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
timestamp: timestamp,
|
|
6
|
-
order_type: 'CD1',
|
|
7
|
-
order_attribute: 'OZHNN',
|
|
8
|
-
order_params: {},
|
|
9
|
-
num_segments: 1,
|
|
10
|
-
mutable: { TransactionPhase: 'Initialisation' }
|
|
11
|
-
)
|
|
2
|
+
def to_xml
|
|
3
|
+
builder = request_factory.create_azv(document_digest, transaction_key, **options)
|
|
4
|
+
builder.to_xml
|
|
12
5
|
end
|
|
13
6
|
end
|
data/lib/epics/b2b.rb
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
class Epics::B2B < Epics::GenericUploadRequest
|
|
2
|
-
def
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
timestamp: timestamp,
|
|
6
|
-
order_type: 'B2B',
|
|
7
|
-
order_attribute: 'OZHNN',
|
|
8
|
-
order_params: {},
|
|
9
|
-
num_segments: 1,
|
|
10
|
-
mutable: { TransactionPhase: 'Initialisation' }
|
|
11
|
-
)
|
|
2
|
+
def to_xml
|
|
3
|
+
builder = request_factory.create_b2b(document_digest, transaction_key, **options)
|
|
4
|
+
builder.to_xml
|
|
12
5
|
end
|
|
13
6
|
end
|
data/lib/epics/bka.rb
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
class Epics::BKA < Epics::GenericRequest
|
|
2
|
-
def
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
timestamp: timestamp,
|
|
6
|
-
order_type: 'BKA',
|
|
7
|
-
order_attribute: 'DZHNN',
|
|
8
|
-
order_params: {
|
|
9
|
-
DateRange: {
|
|
10
|
-
Start: options[:from],
|
|
11
|
-
End: options[:to]
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
mutable: { TransactionPhase: 'Initialisation' }
|
|
15
|
-
)
|
|
2
|
+
def to_xml
|
|
3
|
+
builder = request_factory.create_bka(options[:from], options[:to])
|
|
4
|
+
builder.to_xml
|
|
16
5
|
end
|
|
17
6
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class Epics::Builders::BodyBuilder::Base
|
|
2
|
+
def initialize
|
|
3
|
+
Nokogiri::XML::Builder.new do |xml|
|
|
4
|
+
@xml = xml
|
|
5
|
+
xml.body do
|
|
6
|
+
yield self
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_data_transfer
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_transfer_receipt
|
|
16
|
+
instance = Epics::Builders::TransferReceiptBuilder.new do |instance|
|
|
17
|
+
yield instance
|
|
18
|
+
end
|
|
19
|
+
@xml.parent.add_child(instance.doc)
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def doc
|
|
24
|
+
@xml.doc.root
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class Epics::Builders::DataEncryptionInfoBuilder
|
|
2
|
+
def initialize
|
|
3
|
+
@crypt_service = Epics::Services::CryptService.new
|
|
4
|
+
Nokogiri::XML::Builder.new do |xml|
|
|
5
|
+
@xml = xml
|
|
6
|
+
xml.DataEncryptionInfo(authenticate: true) do
|
|
7
|
+
yield self
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add_encryption_pubkey_digest(keyring, algorithm = 'sha256')
|
|
13
|
+
resolver = Epics::Services::DigestResolver::Base.for_version(keyring.version)
|
|
14
|
+
certificate_digest = resolver.sign_digest(keyring.bank_encryption, algorithm)
|
|
15
|
+
attribues = { Version: keyring.bank_encryption.version, Algorithm: "http://www.w3.org/2001/04/xmlenc##{algorithm}" }
|
|
16
|
+
@xml.EncryptionPubKeyDigest Base64.strict_encode64(certificate_digest), **attribues
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_transaction_key(transaction_key, keyring)
|
|
21
|
+
transaction_key_encrypted = @crypt_service.encrypt_transaction_key(keyring.bank_encryption.key, transaction_key)
|
|
22
|
+
@xml.TransactionKey Base64.strict_encode64(transaction_key_encrypted)
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def doc
|
|
27
|
+
@xml.doc.root
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
class Epics::Builders::DataTransferBuilder::Base
|
|
2
|
+
def initialize
|
|
3
|
+
@crypt_service = Epics::Services::CryptService.new
|
|
4
|
+
Nokogiri::XML::Builder.new do |xml|
|
|
5
|
+
@xml = xml
|
|
6
|
+
xml.DataTransfer do
|
|
7
|
+
yield self
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add_order_data(order_data = nil, transaction_key = nil)
|
|
13
|
+
content = if order_data
|
|
14
|
+
order_data_compressed = Zlib::Deflate.deflate(order_data)
|
|
15
|
+
|
|
16
|
+
Base64.strict_encode64(if transaction_key
|
|
17
|
+
@crypt_service.encrypt_by_key(transaction_key, order_data_compressed)
|
|
18
|
+
else
|
|
19
|
+
order_data_compressed
|
|
20
|
+
end)
|
|
21
|
+
end
|
|
22
|
+
@xml.OrderData content
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_data_encryption_info
|
|
27
|
+
instance = Epics::Builders::DataEncryptionInfoBuilder.new do |instance|
|
|
28
|
+
yield instance
|
|
29
|
+
end
|
|
30
|
+
@xml.parent.add_child(instance.doc)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def add_signature_data(data, transaction_key)
|
|
35
|
+
user_signature_compressed = Zlib::Deflate.deflate(data)
|
|
36
|
+
user_signature_compressed_encrypted = @crypt_service.encrypt_by_key(transaction_key, user_signature_compressed)
|
|
37
|
+
@xml.SignatureData Base64.strict_encode64(user_signature_compressed_encrypted), authenticate: true
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def add_data_digest(signature_version, digest = nil)
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def add_additional_order_info
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def doc
|
|
50
|
+
@xml.doc.root
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class Epics::Builders::DataTransferBuilder::V3 < Epics::Builders::DataTransferBuilder::Base
|
|
2
|
+
def add_data_digest(signature_version, digest = nil)
|
|
3
|
+
digest_value = digest ? Base64.strict_encode64(digest) : nil
|
|
4
|
+
@xml.DataDigest digest_value, SignatureVersion: signature_version
|
|
5
|
+
self
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def add_additional_order_info
|
|
9
|
+
@xml.AdditionalOrderInfo
|
|
10
|
+
self
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class Epics::Builders::HeaderBuilder::Base
|
|
2
|
+
def initialize
|
|
3
|
+
Nokogiri::XML::Builder.new do |xml|
|
|
4
|
+
@xml = xml
|
|
5
|
+
xml.header(authenticate: true) do
|
|
6
|
+
yield self
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_static
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_mutable
|
|
16
|
+
instance = Epics::Builders::MutableBuilder.new do |instance|
|
|
17
|
+
yield instance if block_given?
|
|
18
|
+
end
|
|
19
|
+
@xml.parent.add_child(instance.doc)
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def doc
|
|
24
|
+
@xml.doc.root
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class Epics::Builders::MutableBuilder
|
|
2
|
+
PHASE_INITIALIZATION = 'Initialisation'
|
|
3
|
+
PHASE_RECEIPT = 'Receipt'
|
|
4
|
+
PHASE_TRANSFER = 'Transfer'
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
Nokogiri::XML::Builder.new do |xml|
|
|
8
|
+
@xml = xml
|
|
9
|
+
xml.mutable do
|
|
10
|
+
yield self
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_transaction_phase(transaction_phase)
|
|
16
|
+
@xml.TransactionPhase transaction_phase
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_segment_number(segment_number, is_last_segment = false)
|
|
21
|
+
@xml.SegmentNumber segment_number, lastSegment: is_last_segment
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def doc
|
|
26
|
+
@xml.doc.root
|
|
27
|
+
end
|
|
28
|
+
end
|