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.
Files changed (245) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.semaphore/semaphore.yml +17 -8
  4. data/CHANGELOG.md +93 -0
  5. data/EBICS_REFERENCES.md +169 -0
  6. data/README.md +219 -50
  7. data/lib/epics/azv.rb +3 -10
  8. data/lib/epics/b2b.rb +3 -10
  9. data/lib/epics/bka.rb +3 -14
  10. data/lib/epics/builders/body_builder/base.rb +26 -0
  11. data/lib/epics/builders/body_builder/v2.rb +9 -0
  12. data/lib/epics/builders/body_builder/v3.rb +9 -0
  13. data/lib/epics/builders/body_builder.rb +6 -0
  14. data/lib/epics/builders/data_encryption_info_builder.rb +29 -0
  15. data/lib/epics/builders/data_transfer_builder/base.rb +52 -0
  16. data/lib/epics/builders/data_transfer_builder/v2.rb +9 -0
  17. data/lib/epics/builders/data_transfer_builder/v3.rb +12 -0
  18. data/lib/epics/builders/data_transfer_builder.rb +6 -0
  19. data/lib/epics/builders/header_builder/base.rb +26 -0
  20. data/lib/epics/builders/header_builder/v2.rb +9 -0
  21. data/lib/epics/builders/header_builder/v3.rb +9 -0
  22. data/lib/epics/builders/header_builder.rb +6 -0
  23. data/lib/epics/builders/mutable_builder.rb +28 -0
  24. data/lib/epics/builders/order_details_builder/base.rb +100 -0
  25. data/lib/epics/builders/order_details_builder/v2.rb +21 -0
  26. data/lib/epics/builders/order_details_builder/v3.rb +51 -0
  27. data/lib/epics/builders/order_details_builder.rb +10 -0
  28. data/lib/epics/builders/request_builder.rb +25 -0
  29. data/lib/epics/builders/static_builder/base.rb +75 -0
  30. data/lib/epics/builders/static_builder/v2.rb +9 -0
  31. data/lib/epics/builders/static_builder/v3.rb +9 -0
  32. data/lib/epics/builders/static_builder.rb +9 -0
  33. data/lib/epics/builders/transfer_receipt_builder.rb +19 -0
  34. data/lib/epics/builders/xml_builder/base.rb +92 -0
  35. data/lib/epics/builders/xml_builder/v24.rb +31 -0
  36. data/lib/epics/builders/xml_builder/v25.rb +31 -0
  37. data/lib/epics/builders/xml_builder/v3.rb +31 -0
  38. data/lib/epics/builders/xml_builder.rb +7 -0
  39. data/lib/epics/builders.rb +13 -0
  40. data/lib/epics/c2s.rb +3 -10
  41. data/lib/epics/c52.rb +2 -14
  42. data/lib/epics/c53.rb +2 -14
  43. data/lib/epics/c54.rb +2 -14
  44. data/lib/epics/c5n.rb +3 -14
  45. data/lib/epics/ccs.rb +3 -10
  46. data/lib/epics/cct.rb +3 -10
  47. data/lib/epics/cd1.rb +3 -10
  48. data/lib/epics/cdb.rb +3 -10
  49. data/lib/epics/cdd.rb +3 -10
  50. data/lib/epics/cds.rb +3 -10
  51. data/lib/epics/cdz.rb +3 -14
  52. data/lib/epics/cip.rb +3 -10
  53. data/lib/epics/client.rb +489 -125
  54. data/lib/epics/crypt/aes.rb +56 -0
  55. data/lib/epics/crypt/x_509.rb +52 -0
  56. data/lib/epics/crypt.rb +5 -0
  57. data/lib/epics/crz.rb +3 -14
  58. data/lib/epics/factories/crypt/aes_factory.rb +5 -0
  59. data/lib/epics/factories/crypt.rb +4 -0
  60. data/lib/epics/factories/request_factory/base.rb +236 -0
  61. data/lib/epics/factories/request_factory/v2.rb +194 -0
  62. data/lib/epics/factories/request_factory/v24.rb +32 -0
  63. data/lib/epics/factories/request_factory/v25.rb +31 -0
  64. data/lib/epics/factories/request_factory/v3.rb +305 -0
  65. data/lib/epics/factories/request_factory.rb +8 -0
  66. data/lib/epics/factories.rb +5 -0
  67. data/lib/epics/fdl.rb +3 -18
  68. data/lib/epics/ful.rb +3 -15
  69. data/lib/epics/generic_request.rb +31 -92
  70. data/lib/epics/generic_upload_request.rb +14 -67
  71. data/lib/epics/haa.rb +3 -9
  72. data/lib/epics/hac.rb +3 -14
  73. data/lib/epics/handlers/auth_signature_handler.rb +102 -0
  74. data/lib/epics/handlers/order_data_handler/base.rb +98 -0
  75. data/lib/epics/handlers/order_data_handler/v2.rb +39 -0
  76. data/lib/epics/handlers/order_data_handler/v24.rb +11 -0
  77. data/lib/epics/handlers/order_data_handler/v25.rb +11 -0
  78. data/lib/epics/handlers/order_data_handler/v3.rb +41 -0
  79. data/lib/epics/handlers/order_data_handler.rb +8 -0
  80. data/lib/epics/handlers/user_signature_handler/base.rb +10 -0
  81. data/lib/epics/handlers/user_signature_handler/v2.rb +17 -0
  82. data/lib/epics/handlers/user_signature_handler/v3.rb +17 -0
  83. data/lib/epics/handlers/user_signature_handler.rb +6 -0
  84. data/lib/epics/handlers.rb +6 -0
  85. data/lib/epics/hev.rb +2 -15
  86. data/lib/epics/hia.rb +4 -55
  87. data/lib/epics/hkd.rb +3 -9
  88. data/lib/epics/hpb.rb +3 -13
  89. data/lib/epics/hpd.rb +3 -9
  90. data/lib/epics/htd.rb +3 -9
  91. data/lib/epics/ini.rb +4 -46
  92. data/lib/epics/keyring.rb +17 -0
  93. data/lib/epics/letter_renderer.rb +17 -22
  94. data/lib/epics/ptk.rb +3 -14
  95. data/lib/epics/response.rb +34 -29
  96. data/lib/epics/services/crypt_service.rb +62 -0
  97. data/lib/epics/services/digest_resolver/base.rb +30 -0
  98. data/lib/epics/services/digest_resolver/v2.rb +13 -0
  99. data/lib/epics/services/digest_resolver/v3.rb +19 -0
  100. data/lib/epics/services/digest_resolver.rb +6 -0
  101. data/lib/epics/services.rb +5 -0
  102. data/lib/epics/signature.rb +50 -0
  103. data/lib/epics/{key.rb → signature_algorithm/base.rb} +14 -21
  104. data/lib/epics/signature_algorithm/rsa.rb +50 -0
  105. data/lib/epics/signature_algorithm/rsapkcs1.rb +16 -0
  106. data/lib/epics/signature_algorithm/rsapss.rb +20 -0
  107. data/lib/epics/signature_algorithm.rb +2 -0
  108. data/lib/epics/sta.rb +3 -14
  109. data/lib/epics/version.rb +1 -1
  110. data/lib/epics/vmk.rb +3 -14
  111. data/lib/epics/wss.rb +3 -9
  112. data/lib/epics/xct.rb +3 -10
  113. data/lib/epics/xds.rb +3 -10
  114. data/lib/epics/xe2.rb +3 -10
  115. data/lib/epics/xe3.rb +3 -10
  116. data/lib/epics/xek.rb +5 -0
  117. data/lib/epics/yct.rb +6 -0
  118. data/lib/epics/z01.rb +2 -14
  119. data/lib/epics/z52.rb +2 -14
  120. data/lib/epics/z53.rb +2 -14
  121. data/lib/epics/z54.rb +2 -14
  122. data/lib/epics/zsr.rb +5 -0
  123. data/lib/epics.rb +68 -5
  124. data/lib/letter/ini.erb +27 -6
  125. data/spec/aes_spec.rb +46 -0
  126. data/spec/auth_signature_handler_spec.rb +58 -0
  127. data/spec/builders_spec.rb +616 -0
  128. data/spec/certificate_handling_spec.rb +189 -0
  129. data/spec/client_setup_block_spec.rb +41 -0
  130. data/spec/client_spec.rb +143 -91
  131. data/spec/crypt_service_spec.rb +132 -0
  132. data/spec/date_range_spec.rb +46 -0
  133. data/spec/digest_resolver_spec.rb +164 -0
  134. data/spec/fixtures/bank_x509.pem +3 -0
  135. data/spec/fixtures/xml/haa.xml +40 -1
  136. data/spec/fixtures/xml/haa_receipt.xml +33 -30
  137. data/spec/fixtures/xml/haa_response_h004.xml +4 -0
  138. data/spec/fixtures/xml/haa_response_h005.xml +38 -0
  139. data/spec/fixtures/xml/hia_request_order_data.xml +24 -1
  140. data/spec/fixtures/xml/hpb_request.xml +1 -1
  141. data/spec/fixtures/xml/hpb_response_x509.xml +16 -0
  142. data/spec/fixtures/xml/htd_order_data_h005.xml +55 -0
  143. data/spec/fixtures/xml/jruby/hia.xml +20 -22
  144. data/spec/fixtures/xml/jruby/ini.xml +1 -3
  145. data/spec/fixtures/xml/ruby/hia.xml +21 -1
  146. data/spec/fixtures/xml/ruby/ini.xml +21 -1
  147. data/spec/handlers_spec.rb +267 -0
  148. data/spec/hpb_malformed_certificate_spec.rb +36 -0
  149. data/spec/key_file_decryption_spec.rb +27 -0
  150. data/spec/key_file_preservation_spec.rb +78 -0
  151. data/spec/keyring_spec.rb +349 -0
  152. data/spec/middleware/parse_ebics_spec.rb +2 -1
  153. data/spec/order_id_spec.rb +65 -0
  154. data/spec/orders/azv_spec.rb +63 -7
  155. data/spec/orders/b2b_spec.rb +68 -3
  156. data/spec/orders/bka_spec.rb +34 -4
  157. data/spec/orders/c2s_spec.rb +62 -9
  158. data/spec/orders/c52_spec.rb +34 -4
  159. data/spec/orders/c53_spec.rb +34 -4
  160. data/spec/orders/c54_spec.rb +36 -4
  161. data/spec/orders/c5n_spec.rb +36 -4
  162. data/spec/orders/camt_parity_spec.rb +80 -0
  163. data/spec/orders/ccs_spec.rb +62 -9
  164. data/spec/orders/cct_spec.rb +83 -9
  165. data/spec/orders/cd1_spec.rb +50 -7
  166. data/spec/orders/cdb_spec.rb +64 -7
  167. data/spec/orders/cdd_spec.rb +64 -7
  168. data/spec/orders/cds_spec.rb +74 -0
  169. data/spec/orders/cdz_spec.rb +67 -4
  170. data/spec/orders/cip_spec.rb +61 -9
  171. data/spec/orders/crz_spec.rb +67 -4
  172. data/spec/orders/fdl_spec.rb +143 -13
  173. data/spec/orders/ful_spec.rb +51 -10
  174. data/spec/orders/haa_spec.rb +115 -9
  175. data/spec/orders/hac_spec.rb +89 -10
  176. data/spec/orders/hev_spec.rb +1 -6
  177. data/spec/orders/hia_spec.rb +25 -22
  178. data/spec/orders/hkd_spec.rb +23 -4
  179. data/spec/orders/hpb_spec.rb +7 -4
  180. data/spec/orders/hpd_spec.rb +23 -4
  181. data/spec/orders/htd_spec.rb +23 -4
  182. data/spec/orders/ini_spec.rb +23 -18
  183. data/spec/orders/ptk_spec.rb +27 -4
  184. data/spec/orders/sta_spec.rb +129 -11
  185. data/spec/orders/vmk_spec.rb +36 -4
  186. data/spec/orders/wss_spec.rb +29 -3
  187. data/spec/orders/x_509_certificate_spec.rb +9 -1
  188. data/spec/orders/xct_spec.rb +63 -0
  189. data/spec/orders/xds_spec.rb +13 -6
  190. data/spec/orders/xe2_spec.rb +64 -7
  191. data/spec/orders/xe3_spec.rb +64 -7
  192. data/spec/orders/xek_spec.rb +31 -0
  193. data/spec/orders/yct_spec.rb +43 -0
  194. data/spec/orders/z01_spec.rb +34 -4
  195. data/spec/orders/z52_spec.rb +34 -4
  196. data/spec/orders/z53_spec.rb +34 -4
  197. data/spec/orders/z54_spec.rb +36 -4
  198. data/spec/orders/zsr_spec.rb +31 -0
  199. data/spec/request_factory_spec.rb +444 -0
  200. data/spec/response_business_error_spec.rb +45 -0
  201. data/spec/response_validation_spec.rb +23 -0
  202. data/spec/{key_spec.rb → signature_algorithm/rsa_pss_spec.rb} +4 -13
  203. data/spec/support/ebics_matcher.rb +17 -9
  204. data/spec/support/ebics_shared_examples.rb +642 -0
  205. data/spec/support/h005_client_helper.rb +23 -0
  206. data/spec/upload_digest_spec.rb +69 -0
  207. data/spec/user_signature_handler_spec.rb +25 -0
  208. data/spec/version_support_error_spec.rb +76 -0
  209. data/spec/xsd/{ebics_hev.xsd → H000/ebics_hev.xsd} +0 -0
  210. data/spec/xsd/H003/ebics.xsd +11 -0
  211. data/spec/xsd/H003/ebics_keymgmt_request.xsd +555 -0
  212. data/spec/xsd/H003/ebics_keymgmt_response.xsd +131 -0
  213. data/spec/xsd/H003/ebics_orders.xsd +1811 -0
  214. data/spec/xsd/H003/ebics_request.xsd +354 -0
  215. data/spec/xsd/H003/ebics_response.xsd +161 -0
  216. data/spec/xsd/{ebics_signature.xsd → H003/ebics_signature.xsd} +0 -0
  217. data/spec/xsd/H003/ebics_types.xsd +2361 -0
  218. data/spec/xsd/H003/xmldsig-core-schema.xsd +318 -0
  219. data/spec/xsd/H004/ebics_signature.xsd +217 -0
  220. data/spec/xsd/H005/ebics_H005.xsd +11 -0
  221. data/spec/xsd/H005/ebics_keymgmt_request_H005.xsd +523 -0
  222. data/spec/xsd/H005/ebics_keymgmt_response_H005.xsd +137 -0
  223. data/spec/xsd/H005/ebics_orders_H005.xsd +2094 -0
  224. data/spec/xsd/H005/ebics_request_H005.xsd +349 -0
  225. data/spec/xsd/H005/ebics_response_H005.xsd +167 -0
  226. data/spec/xsd/H005/ebics_signature_S002.xsd +177 -0
  227. data/spec/xsd/H005/ebics_types_H005.xsd +1885 -0
  228. data/spec/xsd/H005/xmldsig-core-schema.xsd +318 -0
  229. metadata +197 -36
  230. data/lib/epics/header_request.rb +0 -58
  231. data/lib/epics/middleware/xmlsig.rb +0 -16
  232. data/lib/epics/signer.rb +0 -40
  233. data/lib/epics/x_509_certificate.rb +0 -15
  234. data/lib/letter/ini_with_certs.erb +0 -335
  235. data/spec/generic_upload_spec.rb +0 -58
  236. data/spec/hpb_spec.rb +0 -15
  237. data/spec/signer_spec.rb +0 -34
  238. /data/spec/xsd/{ebics_H004.xsd → H004/ebics_H004.xsd} +0 -0
  239. /data/spec/xsd/{ebics_keymgmt_request_H004.xsd → H004/ebics_keymgmt_request_H004.xsd} +0 -0
  240. /data/spec/xsd/{ebics_keymgmt_response_H004.xsd → H004/ebics_keymgmt_response_H004.xsd} +0 -0
  241. /data/spec/xsd/{ebics_orders_H004.xsd → H004/ebics_orders_H004.xsd} +0 -0
  242. /data/spec/xsd/{ebics_request_H004.xsd → H004/ebics_request_H004.xsd} +0 -0
  243. /data/spec/xsd/{ebics_response_H004.xsd → H004/ebics_response_H004.xsd} +0 -0
  244. /data/spec/xsd/{ebics_types_H004.xsd → H004/ebics_types_H004.xsd} +0 -0
  245. /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", 4096)
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 ;). In case you're having difficulties
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
- 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).
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
- You can generate your own X.509 certificate using Ruby’s OpenSSL library:
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
- 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)
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
- **Note:** For production environments, your bank may require certificates issued by a trusted authority. Be sure to confirm your bank’s requirements before proceeding.
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
- #### 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")
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: certificate_a,
259
- x_509_certificate_x_content: certificate_x,
260
- x_509_certificate_e_content: certificate_e,
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
- renderer = Epics::LetterRenderer.new(client)
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
- If all three certificates are present, the INI letter will use certificate hashes as required for certificate-based registration.
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 header
3
- client.header_request.build(
4
- nonce: nonce,
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 header
3
- client.header_request.build(
4
- nonce: nonce,
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 header
3
- client.header_request.build(
4
- nonce: nonce,
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,9 @@
1
+ class Epics::Builders::BodyBuilder::V2 < Epics::Builders::BodyBuilder::Base
2
+ def add_data_transfer
3
+ instance = Epics::Builders::DataTransferBuilder::V2.new do |instance|
4
+ yield instance
5
+ end
6
+ @xml.parent.add_child(instance.doc)
7
+ self
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Epics::Builders::BodyBuilder::V3 < Epics::Builders::BodyBuilder::Base
2
+ def add_data_transfer
3
+ instance = Epics::Builders::DataTransferBuilder::V3.new do |instance|
4
+ yield instance
5
+ end
6
+ @xml.parent.add_child(instance.doc)
7
+ self
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Epics::Builders::BodyBuilder
2
+ end
3
+
4
+ require 'epics/builders/body_builder/base'
5
+ require 'epics/builders/body_builder/v2'
6
+ require 'epics/builders/body_builder/v3'
@@ -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,9 @@
1
+ class Epics::Builders::DataTransferBuilder::V2 < Epics::Builders::DataTransferBuilder::Base
2
+ def add_data_digest(signature_version, digest = nil)
3
+ self
4
+ end
5
+
6
+ def add_additional_order_info
7
+ self
8
+ end
9
+ 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,6 @@
1
+ module Epics::Builders::DataTransferBuilder
2
+ end
3
+
4
+ require 'epics/builders/data_transfer_builder/base'
5
+ require 'epics/builders/data_transfer_builder/v2'
6
+ require 'epics/builders/data_transfer_builder/v3'
@@ -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,9 @@
1
+ class Epics::Builders::HeaderBuilder::V2 < Epics::Builders::HeaderBuilder::Base
2
+ def add_static
3
+ instance = Epics::Builders::StaticBuilder::V2.new do |instance|
4
+ yield instance
5
+ end
6
+ @xml.parent.add_child(instance.doc)
7
+ self
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Epics::Builders::HeaderBuilder::V3 < Epics::Builders::HeaderBuilder::Base
2
+ def add_static
3
+ instance = Epics::Builders::StaticBuilder::V3.new do |instance|
4
+ yield instance
5
+ end
6
+ @xml.parent.add_child(instance.doc)
7
+ self
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Epics::Builders::HeaderBuilder
2
+ end
3
+
4
+ require 'epics/builders/header_builder/base'
5
+ require 'epics/builders/header_builder/v2'
6
+ require 'epics/builders/header_builder/v3'
@@ -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