epics 2.10.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 +97 -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 +491 -123
  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 +6 -0
  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 +69 -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 +63 -0
  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 +200 -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
@@ -0,0 +1,267 @@
1
+ RSpec.describe 'Handlers' do
2
+ let(:client) { Epics::Client.new(File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')), 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS', version:) }
3
+ let(:version) { Epics::Keyring::VERSION_25 }
4
+
5
+ describe Epics::Handlers::UserSignatureHandler do
6
+ let(:digest) { OpenSSL::Digest::SHA256.digest('test document') }
7
+
8
+ describe '::Base' do
9
+ it 'raises NotImplementedError' do
10
+ base = described_class::Base.new(client)
11
+ expect { base.handle(digest) }.to raise_error(NotImplementedError)
12
+ end
13
+ end
14
+
15
+ describe '::V2' do
16
+ subject(:handler) { described_class::V2.new(client) }
17
+
18
+ it 'generates UserSignatureData with S001 namespace' do
19
+ result = handler.handle(digest)
20
+ xml = Nokogiri::XML(result.to_xml)
21
+ expect(xml.root.name).to eq('UserSignatureData')
22
+ expect(xml.root.namespace.href).to eq('http://www.ebics.org/S001')
23
+ end
24
+
25
+ it 'includes OrderSignatureData with correct structure' do
26
+ result = handler.handle(digest)
27
+ xml = Nokogiri::XML(result.to_xml)
28
+ xml.remove_namespaces!
29
+ expect(xml.at('//SignatureVersion').text).to eq(client.keyring.user_signature.version)
30
+ expect(xml.at('//PartnerID').text).to eq(client.partner_id)
31
+ expect(xml.at('//UserID').text).to eq(client.user_id)
32
+ end
33
+
34
+ it 'produces a verifiable SignatureValue' do
35
+ result = handler.handle(digest)
36
+ xml = Nokogiri::XML(result.to_xml)
37
+ xml.remove_namespaces!
38
+ sig_value = xml.at('//SignatureValue').text
39
+ expect(client.keyring.user_signature.key.verify(sig_value, digest)).to be true
40
+ end
41
+ end
42
+
43
+ describe '::V3' do
44
+ let(:version) { Epics::Keyring::VERSION_30 }
45
+ subject(:handler) { described_class::V3.new(client) }
46
+
47
+ it 'generates UserSignatureData with S002 namespace' do
48
+ result = handler.handle(digest)
49
+ xml = Nokogiri::XML(result.to_xml)
50
+ expect(xml.root.name).to eq('UserSignatureData')
51
+ expect(xml.root.namespace.href).to eq('http://www.ebics.org/S002')
52
+ end
53
+
54
+ it 'includes OrderSignatureData with correct structure' do
55
+ result = handler.handle(digest)
56
+ xml = Nokogiri::XML(result.to_xml)
57
+ xml.remove_namespaces!
58
+ expect(xml.at('//SignatureVersion').text).to eq(client.keyring.user_signature.version)
59
+ expect(xml.at('//PartnerID').text).to eq(client.partner_id)
60
+ expect(xml.at('//UserID').text).to eq(client.user_id)
61
+ end
62
+
63
+ it 'produces a verifiable SignatureValue' do
64
+ result = handler.handle(digest)
65
+ xml = Nokogiri::XML(result.to_xml)
66
+ xml.remove_namespaces!
67
+ sig_value = xml.at('//SignatureValue').text
68
+ expect(client.keyring.user_signature.key.verify(sig_value, digest)).to be true
69
+ end
70
+ end
71
+
72
+ describe 'V2 vs V3 namespace difference' do
73
+ let(:digest) { OpenSSL::Digest::SHA256.digest('test') }
74
+
75
+ it 'V2 uses S001, V3 uses S002' do
76
+ v2_client = Epics::Client.new(File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')), 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS', version: Epics::Keyring::VERSION_25)
77
+ v3_client = Epics::Client.new(File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')), 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS', version: Epics::Keyring::VERSION_30)
78
+
79
+ v2_xml = Nokogiri::XML(described_class::V2.new(v2_client).handle(digest).to_xml)
80
+ v3_xml = Nokogiri::XML(described_class::V3.new(v3_client).handle(digest).to_xml)
81
+ expect(v2_xml.root.namespace.href).to eq('http://www.ebics.org/S001')
82
+ expect(v3_xml.root.namespace.href).to eq('http://www.ebics.org/S002')
83
+ end
84
+ end
85
+ end
86
+
87
+ describe Epics::Handlers::OrderDataHandler do
88
+ let(:timestamp) { Time.utc(2024, 6, 15, 10, 30, 0) }
89
+
90
+ shared_examples 'an OrderDataHandler' do |handler_class, hia_namespace, sig_namespace|
91
+ subject(:handler) { handler_class.new(client) }
92
+
93
+ describe '#handle_ini' do
94
+ it 'generates SignaturePubKeyOrderData XML' do
95
+ handler.handle_ini(client.keyring.user_signature, timestamp)
96
+ xml = Nokogiri::XML(handler.to_xml)
97
+ xml.remove_namespaces!
98
+ expect(xml.at('SignaturePubKeyOrderData')).not_to be_nil
99
+ end
100
+
101
+ it "uses #{sig_namespace} namespace" do
102
+ handler.handle_ini(client.keyring.user_signature, timestamp)
103
+ xml = Nokogiri::XML(handler.to_xml)
104
+ node = xml.root
105
+ expect(node.namespace.href).to eq(sig_namespace)
106
+ end
107
+
108
+ it 'includes SignatureVersion' do
109
+ handler.handle_ini(client.keyring.user_signature, timestamp)
110
+ xml = Nokogiri::XML(handler.to_xml)
111
+ xml.remove_namespaces!
112
+ expect(xml.at('//SignatureVersion').text).to eq(client.keyring.user_signature.version)
113
+ end
114
+
115
+ it 'includes PartnerID and UserID' do
116
+ handler.handle_ini(client.keyring.user_signature, timestamp)
117
+ xml = Nokogiri::XML(handler.to_xml)
118
+ xml.remove_namespaces!
119
+ expect(xml.at('//PartnerID').text).to eq(client.partner_id)
120
+ expect(xml.at('//UserID').text).to eq(client.user_id)
121
+ end
122
+ end
123
+
124
+ describe '#handle_hia' do
125
+ it 'generates HIARequestOrderData XML' do
126
+ handler.handle_hia(client.keyring.user_authentication, client.keyring.user_encryption, timestamp)
127
+ xml = Nokogiri::XML(handler.to_xml)
128
+ xml.remove_namespaces!
129
+ expect(xml.at('HIARequestOrderData')).not_to be_nil
130
+ end
131
+
132
+ it 'includes AuthenticationVersion and EncryptionVersion' do
133
+ handler.handle_hia(client.keyring.user_authentication, client.keyring.user_encryption, timestamp)
134
+ xml = Nokogiri::XML(handler.to_xml)
135
+ xml.remove_namespaces!
136
+ expect(xml.at('//AuthenticationVersion').text).to eq(client.keyring.user_authentication.version)
137
+ expect(xml.at('//EncryptionVersion').text).to eq(client.keyring.user_encryption.version)
138
+ end
139
+
140
+ it 'includes PartnerID and UserID' do
141
+ handler.handle_hia(client.keyring.user_authentication, client.keyring.user_encryption, timestamp)
142
+ xml = Nokogiri::XML(handler.to_xml)
143
+ xml.remove_namespaces!
144
+ expect(xml.at('//PartnerID').text).to eq(client.partner_id)
145
+ expect(xml.at('//UserID').text).to eq(client.user_id)
146
+ end
147
+ end
148
+ end
149
+
150
+ context 'V24 (H003)' do
151
+ let(:version) { Epics::Keyring::VERSION_24 }
152
+ include_examples 'an OrderDataHandler',
153
+ Epics::Handlers::OrderDataHandler::V24, 'http://www.ebics.org/H003', 'http://www.ebics.org/S001'
154
+
155
+ it 'includes PubKeyValue with RSA key material in INI' do
156
+ handler = Epics::Handlers::OrderDataHandler::V24.new(client)
157
+ handler.handle_ini(client.keyring.user_signature, timestamp)
158
+ xml = Nokogiri::XML(handler.to_xml)
159
+ xml.remove_namespaces!
160
+ expect(xml.at('//RSAKeyValue/Modulus')).not_to be_nil
161
+ expect(xml.at('//RSAKeyValue/Exponent')).not_to be_nil
162
+ expect(xml.at('//PubKeyValue/TimeStamp').text).to eq('2024-06-15T10:30:00Z')
163
+ end
164
+ end
165
+
166
+ context 'V25 (H004)' do
167
+ let(:version) { Epics::Keyring::VERSION_25 }
168
+ include_examples 'an OrderDataHandler',
169
+ Epics::Handlers::OrderDataHandler::V25, 'urn:org:ebics:H004', 'http://www.ebics.org/S001'
170
+
171
+ it 'includes PubKeyValue with RSA key material in HIA (2 keys)' do
172
+ handler = Epics::Handlers::OrderDataHandler::V25.new(client)
173
+ handler.handle_hia(client.keyring.user_authentication, client.keyring.user_encryption, timestamp)
174
+ xml = Nokogiri::XML(handler.to_xml)
175
+ xml.remove_namespaces!
176
+ moduli = xml.xpath('//RSAKeyValue/Modulus')
177
+ expect(moduli.size).to eq(2)
178
+ end
179
+ end
180
+
181
+ context 'V3 (H005)' do
182
+ let(:version) { Epics::Keyring::VERSION_30 }
183
+ include_examples 'an OrderDataHandler',
184
+ Epics::Handlers::OrderDataHandler::V3, 'urn:org:ebics:H005', 'http://www.ebics.org/S002'
185
+
186
+ it 'does NOT include PubKeyValue (relies on certificates)' do
187
+ handler = Epics::Handlers::OrderDataHandler::V3.new(client)
188
+ handler.handle_ini(client.keyring.user_signature, timestamp)
189
+ xml = Nokogiri::XML(handler.to_xml)
190
+ xml.remove_namespaces!
191
+ expect(xml.at('//PubKeyValue')).to be_nil
192
+ end
193
+
194
+ it 'includes X509Data when certificate is present' do
195
+ handler = Epics::Handlers::OrderDataHandler::V3.new(client)
196
+ handler.handle_ini(client.keyring.user_signature, timestamp)
197
+ xml = Nokogiri::XML(handler.to_xml)
198
+ xml.remove_namespaces!
199
+ expect(xml.at('//X509Data')).not_to be_nil
200
+ expect(xml.at('//X509Certificate')).not_to be_nil
201
+ end
202
+ end
203
+ end
204
+
205
+ describe Epics::Handlers::AuthSignatureHandler do
206
+ it 'adds AuthSignature element to XML builder output' do
207
+ hpb = Epics::HPB.new(client)
208
+ xml = Nokogiri::XML(hpb.to_xml)
209
+ xml.remove_namespaces!
210
+ expect(xml.at('//AuthSignature')).not_to be_nil
211
+ end
212
+
213
+ it 'includes ds:SignedInfo with correct algorithms' do
214
+ hpb = Epics::HPB.new(client)
215
+ xml = Nokogiri::XML(hpb.to_xml)
216
+ ds = { 'ds' => 'http://www.w3.org/2000/09/xmldsig#' }
217
+ signed_info = xml.at_xpath('//ds:SignedInfo', ds)
218
+ expect(signed_info).not_to be_nil
219
+
220
+ canon_method = signed_info.at_xpath('ds:CanonicalizationMethod', ds)
221
+ expect(canon_method['Algorithm']).to include('REC-xml-c14n-20010315')
222
+
223
+ sig_method = signed_info.at_xpath('ds:SignatureMethod', ds)
224
+ expect(sig_method['Algorithm']).to include('rsa-sha256')
225
+ end
226
+
227
+ it 'includes ds:DigestValue that matches header hash' do
228
+ allow(SecureRandom).to receive(:hex).with(16).and_return('014a82626a51ee1cab547bbaf18a13a0')
229
+ allow(Time).to receive(:now).and_return(Time.parse('2014-09-09T09:33:12Z'))
230
+
231
+ hpb = Epics::HPB.new(client)
232
+ xml = Nokogiri::XML(hpb.to_xml)
233
+
234
+ headers_node = xml.xpath("//*[@authenticate='true']").first
235
+ crypt_service = Epics::Services::CryptService.new
236
+ expected_hash = crypt_service.hash(headers_node.canonicalize)
237
+
238
+ ds = { 'ds' => 'http://www.w3.org/2000/09/xmldsig#' }
239
+ digest_value = xml.at_xpath('//ds:DigestValue', ds).text
240
+ expect(digest_value).to eq(Base64.strict_encode64(expected_hash))
241
+ end
242
+
243
+ it 'produces a verifiable ds:SignatureValue' do
244
+ hpb = Epics::HPB.new(client)
245
+ xml = Nokogiri::XML(hpb.to_xml)
246
+ ds = { 'ds' => 'http://www.w3.org/2000/09/xmldsig#' }
247
+ sig_value = xml.at_xpath('//ds:SignatureValue', ds).text
248
+ signed_info = xml.at_xpath('//ds:SignedInfo', ds)
249
+ expect(client.keyring.user_authentication.key.verify(sig_value, signed_info.canonicalize)).to be true
250
+ end
251
+
252
+ context 'with different EBICS versions' do
253
+ [Epics::Keyring::VERSION_24, Epics::Keyring::VERSION_25, Epics::Keyring::VERSION_30].each do |ver|
254
+ it "works with version #{ver}" do
255
+ c = Epics::Client.new(
256
+ File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')),
257
+ 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS', version: ver
258
+ )
259
+ hpb = Epics::HPB.new(c)
260
+ xml = Nokogiri::XML(hpb.to_xml)
261
+ xml.remove_namespaces!
262
+ expect(xml.at('//AuthSignature')).not_to be_nil
263
+ end
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,36 @@
1
+ RSpec.describe 'HPB with an unreadable certificate' do
2
+ let(:client) do
3
+ Epics::Client.new(File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')),
4
+ 'secret', 'https://194.180.18.30/ebicsweb/ebicsweb', 'SIZBN001', 'EBIX', 'EBICS',
5
+ version: Epics::Keyring::VERSION_30)
6
+ end
7
+
8
+ let(:order_data) do
9
+ <<~XML
10
+ <?xml version="1.0" encoding="UTF-8"?>
11
+ <HPBResponseOrderData xmlns="urn:org:ebics:H005" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
12
+ <AuthenticationPubKeyInfo>
13
+ <ds:X509Data><ds:X509Certificate>bm90LWEtY2VydGlmaWNhdGU=</ds:X509Certificate></ds:X509Data>
14
+ <AuthenticationVersion>X002</AuthenticationVersion>
15
+ </AuthenticationPubKeyInfo>
16
+ <HostID>SIZBN001</HostID>
17
+ </HPBResponseOrderData>
18
+ XML
19
+ end
20
+
21
+ before { allow(client).to receive(:download).and_return(order_data) }
22
+
23
+ # A keyring built from a partly unreadable HPB response is not usable, and a warning
24
+ # on stderr is easy to miss on a server.
25
+ it 'fails rather than storing an incomplete keyring' do
26
+ expect { client.HPB }.to raise_error(Epics::InvalidCertificateError)
27
+ end
28
+
29
+ it 'says where the certificate came from' do
30
+ expect { client.HPB }.to raise_error(/from the HPB response/)
31
+ end
32
+
33
+ it 'includes the underlying OpenSSL error' do
34
+ expect { client.HPB }.to raise_error(/PEM_read_bio_X509/)
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ RSpec.describe 'key file decryption' do
2
+ let(:client) do
3
+ Epics::Client.new(File.open(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')),
4
+ 'secret', 'https://example.com/ebics', 'SIZBN001', 'EBIX', 'EBICS')
5
+ end
6
+
7
+ it 'round-trips a PEM' do
8
+ pem = client.keyring.user_signature.key.key.to_pem
9
+
10
+ expect(client.send(:decrypt, client.send(:encrypt, pem))).to eq(pem)
11
+ end
12
+
13
+ # The padding check catches a wrong passphrase 255 times out of 256. This is the
14
+ # remaining case: correctly padded plaintext that is not a key.
15
+ it 'rejects correctly padded plaintext that is not a PEM' do
16
+ expect { client.send(:decrypt, client.send(:encrypt, 'not a pem at all')) }
17
+ .to raise_error(OpenSSL::Cipher::CipherError, /wrong passphrase/)
18
+ end
19
+
20
+ it 'fails the same way whichever branch trips' do
21
+ json = client.send(:dump_keys)
22
+
23
+ expect do
24
+ Epics::Client.new(json, 'wrong_passphrase', 'https://example.com/ebics', 'SIZBN001', 'EBIX', 'EBICS')
25
+ end.to raise_error(OpenSSL::Cipher::CipherError)
26
+ end
27
+ end
@@ -0,0 +1,78 @@
1
+ RSpec.describe 'key file preservation' do
2
+ let(:keyfile) { File.read(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key')) }
3
+ let(:url) { 'https://example.com/ebics' }
4
+
5
+ def load_client(host_id)
6
+ Epics::Client.new(keyfile, 'secret', url, host_id, 'EBIX', 'EBICS')
7
+ end
8
+
9
+ def entries(client)
10
+ JSON.parse(client.send(:dump_keys))
11
+ end
12
+
13
+ context 'when host_id matches the stored bank keys' do
14
+ subject(:reloaded) { load_client('SIZBN001') }
15
+
16
+ it 'loads the bank keys into the keyring' do
17
+ expect(reloaded.bank_authentication_key).not_to be_nil
18
+ expect(reloaded.bank_encryption_key).not_to be_nil
19
+ end
20
+
21
+ it 'writes every entry back' do
22
+ expect(entries(reloaded).keys).to match_array(JSON.parse(keyfile).keys)
23
+ end
24
+ end
25
+
26
+ # A renamed host, a reused key file or a case difference leaves the bank entries
27
+ # unrecognised. They must survive the round-trip regardless.
28
+ context 'when host_id does not match the stored bank keys' do
29
+ subject(:reloaded) { load_client('OTHERHOST') }
30
+
31
+ it 'cannot place the bank keys' do
32
+ expect(reloaded.bank_authentication_key).to be_nil
33
+ end
34
+
35
+ it 'still writes every entry back' do
36
+ expect(entries(reloaded).keys).to match_array(JSON.parse(keyfile).keys)
37
+ end
38
+
39
+ it 'leaves the unplaced entries untouched' do
40
+ original = JSON.parse(keyfile)
41
+ written = entries(reloaded)
42
+
43
+ %w[SIZBN001.E002 SIZBN001.X002].each do |name|
44
+ expect(written[name]).to eq(original[name])
45
+ end
46
+ end
47
+
48
+ it 'warns rather than dropping them silently' do
49
+ expect { load_client('OTHERHOST') }
50
+ .to output(/keeping "SIZBN001.E002" in the key file unchanged/).to_stderr
51
+ end
52
+ end
53
+
54
+ # A key written by an older EBICS version, still decryptable but of a version this
55
+ # gem no longer builds signatures for.
56
+ context 'with an entry of an unknown signature version' do
57
+ let(:original) { JSON.parse(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'SIZBN001.key'))) }
58
+ let(:keyfile) { JSON.pretty_generate(original.merge('A004' => original['A006'])) }
59
+
60
+ it 'keeps it' do
61
+ reloaded = nil
62
+ expect { reloaded = load_client('SIZBN001') }.to output(/A004/).to_stderr
63
+ expect(entries(reloaded)['A004']).to eq(original['A006'])
64
+ end
65
+
66
+ it 'still loads the keys it does understand' do
67
+ reloaded = nil
68
+ expect { reloaded = load_client('SIZBN001') }.to output(/A004/).to_stderr
69
+ expect(reloaded.signature_key).not_to be_nil
70
+ end
71
+ end
72
+
73
+ # A wrong passphrase must still fail loudly rather than being kept as unmapped.
74
+ it 'raises on an undecryptable key file' do
75
+ expect { Epics::Client.new(keyfile, 'wrong', url, 'SIZBN001', 'EBIX', 'EBICS') }
76
+ .to raise_error(OpenSSL::Cipher::CipherError)
77
+ end
78
+ end