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/lib/epics/client.rb
CHANGED
|
@@ -1,80 +1,251 @@
|
|
|
1
1
|
class Epics::Client
|
|
2
2
|
extend Forwardable
|
|
3
3
|
|
|
4
|
-
attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :
|
|
5
|
-
:
|
|
6
|
-
|
|
4
|
+
attr_accessor :passphrase, :url, :host_id, :user_id, :partner_id, :keys_content, :locale, :product_name,
|
|
5
|
+
:debug_mode
|
|
6
|
+
attr_reader :current_order_id
|
|
7
|
+
attr_reader :keyring
|
|
7
8
|
attr_writer :iban, :bic, :name
|
|
8
|
-
|
|
9
|
+
|
|
9
10
|
def_delegators :connection, :post
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
USER_AGENT = "EPICS v#{Epics::VERSION}"
|
|
13
|
+
|
|
14
|
+
# EBICS 3.0 requires at least 2048 bits.
|
|
15
|
+
DEFAULT_KEY_SIZE = 4096
|
|
16
|
+
|
|
17
|
+
# OrderIDType is `[A-Z][A-Z0-9]{3}`, i.e. base 36 from A000 to ZZZZ. Only EBICS 2.4
|
|
18
|
+
# sends an OrderID; 2.5 and 3.0 leave numbering to the bank.
|
|
19
|
+
MIN_ORDER_ID = 466_560
|
|
20
|
+
MAX_ORDER_ID = 1_679_615
|
|
21
|
+
ORDER_ID_FORMAT = /\A[A-Z][A-Z0-9]{3}\z/
|
|
22
|
+
|
|
23
|
+
# Constructor option carrying certificate content, per signature type.
|
|
24
|
+
CERTIFICATE_OPTIONS = {
|
|
25
|
+
Epics::Signature::TYPE_A => :x_509_certificate_a_content,
|
|
26
|
+
Epics::Signature::TYPE_X => :x_509_certificate_x_content,
|
|
27
|
+
Epics::Signature::TYPE_E => :x_509_certificate_e_content
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
11
30
|
def initialize(keys_content, passphrase, url, host_id, user_id, partner_id, options = {})
|
|
12
|
-
self.
|
|
13
|
-
self.passphrase = passphrase
|
|
14
|
-
self.keys = extract_keys if keys_content
|
|
15
|
-
self.url = url
|
|
31
|
+
self.url = url
|
|
16
32
|
self.host_id = host_id
|
|
17
33
|
self.user_id = user_id
|
|
18
34
|
self.partner_id = partner_id
|
|
19
35
|
self.locale = options[:locale] || Epics::DEFAULT_LOCALE
|
|
20
36
|
self.product_name = options[:product_name] || Epics::DEFAULT_PRODUCT_NAME
|
|
37
|
+
self.current_order_id = options[:order_id] || MIN_ORDER_ID
|
|
38
|
+
@keyring = Epics::Keyring.new(options[:version] || Epics::Keyring::VERSION_25)
|
|
39
|
+
self.keys_content = keys_content.respond_to?(:read) ? keys_content.read : keys_content if keys_content
|
|
40
|
+
self.passphrase = passphrase
|
|
21
41
|
self.debug_mode = !!options[:debug_mode]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
@certificate_contents = CERTIFICATE_OPTIONS.transform_values { |option| options[option] }
|
|
43
|
+
@certificate_subject = options[:certificate_subject]
|
|
44
|
+
@certificate_not_after = options[:certificate_not_after]
|
|
45
|
+
@generate_certificates = options.fetch(:generate_certificates, keyring.version == Epics::Keyring::VERSION_30)
|
|
46
|
+
extract_keys if keys_content
|
|
47
|
+
apply_certificates
|
|
48
|
+
|
|
49
|
+
yield self if block_given?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Attaches the certificates passed to the constructor. `.setup` calls this again
|
|
53
|
+
# because it builds the signatures after `#initialize` has run. Only assigns where
|
|
54
|
+
# content was given, so a certificate loaded from a key file survives.
|
|
55
|
+
def apply_certificates
|
|
56
|
+
user_signatures.each do |type, signature|
|
|
57
|
+
certificate = parse_certificate(@certificate_contents[type], CERTIFICATE_OPTIONS[type])
|
|
58
|
+
signature.certificate = certificate if certificate
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# H005 cannot be initialised without certificates, so `.setup` mints them by default.
|
|
65
|
+
def generate_certificates?
|
|
66
|
+
@generate_certificates
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Self-signed certificate for each user key that lacks one. Permitted for INI/HIA by
|
|
70
|
+
# the Swiss market practice guidelines §6.1; banks requiring CA-issued certificates
|
|
71
|
+
# should be given `x_509_certificate_*_content`, which takes precedence.
|
|
72
|
+
#
|
|
73
|
+
# Called only by `.setup`: regenerating on every `.new` would hand the bank a
|
|
74
|
+
# different fingerprint than the one on the initialisation letter.
|
|
75
|
+
def generate_certificates
|
|
76
|
+
user_signatures.each_value do |signature|
|
|
77
|
+
next if signature.certificate
|
|
78
|
+
|
|
79
|
+
signature.certificate = Epics::Crypt::X509.generate(
|
|
80
|
+
signature.key,
|
|
81
|
+
subject: certificate_subject,
|
|
82
|
+
not_after: @certificate_not_after || Epics::Crypt::X509::UNLIMITED
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Built as an OpenSSL::X509::Name so IDs containing `/` or `=` need no escaping.
|
|
90
|
+
def certificate_subject
|
|
91
|
+
@certificate_subject ||= OpenSSL::X509::Name.new([
|
|
92
|
+
['CN', user_id],
|
|
93
|
+
['OU', partner_id],
|
|
94
|
+
['O', host_id]
|
|
95
|
+
])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def version
|
|
99
|
+
keyring.version
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def urn_schema
|
|
103
|
+
case version
|
|
104
|
+
when Epics::Keyring::VERSION_24
|
|
105
|
+
"http://www.ebics.org/#{version}"
|
|
106
|
+
when Epics::Keyring::VERSION_25, Epics::Keyring::VERSION_30
|
|
107
|
+
"urn:org:ebics:#{version}"
|
|
108
|
+
end
|
|
27
109
|
end
|
|
28
110
|
|
|
29
111
|
def inspect
|
|
30
|
-
"#<#{self.class}:#{
|
|
31
|
-
@
|
|
32
|
-
@
|
|
33
|
-
@
|
|
112
|
+
"#<#{self.class}:#{object_id}
|
|
113
|
+
@version=#{keyring.version},
|
|
114
|
+
@keys=#{keys.keys},
|
|
115
|
+
@user_id=\"#{user_id}\",
|
|
116
|
+
@partner_id=\"#{partner_id}\""
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Accepts either the protocol form ('A000') or the integer it encodes, so a caller
|
|
120
|
+
# can persist whichever it has and hand it back.
|
|
121
|
+
def current_order_id=(value)
|
|
122
|
+
# String#to_i(36) is lenient, so the format is checked before converting.
|
|
123
|
+
id = value.is_a?(String) && value.match?(ORDER_ID_FORMAT) ? value.to_i(36) : value
|
|
124
|
+
|
|
125
|
+
unless id.is_a?(Integer) && id.between?(MIN_ORDER_ID, MAX_ORDER_ID)
|
|
126
|
+
raise ArgumentError,
|
|
127
|
+
"order_id must be between #{MIN_ORDER_ID.to_s(36).upcase} and " \
|
|
128
|
+
"#{MAX_ORDER_ID.to_s(36).upcase}, got #{value.inspect}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
@current_order_id = id
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Not persisted with the keys: read it after use and pass it back as `order_id:` on
|
|
135
|
+
# the next client, or EBICS 2.4 uploads replay IDs the bank has already seen.
|
|
136
|
+
def next_order_id
|
|
137
|
+
raise 'Order ID overflow' if current_order_id >= MAX_ORDER_ID
|
|
138
|
+
|
|
139
|
+
self.current_order_id += 1
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def encryption_version
|
|
143
|
+
keyring.user_encryption&.version
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def encryption_key
|
|
147
|
+
keyring.user_encryption&.key
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def signature_version
|
|
151
|
+
keyring.user_signature&.version
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def signature_key
|
|
155
|
+
keyring.user_signature&.key
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def authentication_version
|
|
159
|
+
keyring.user_authentication&.version
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def authentication_key
|
|
163
|
+
keyring.user_authentication&.key
|
|
34
164
|
end
|
|
35
165
|
|
|
36
|
-
def
|
|
37
|
-
|
|
166
|
+
def bank_encryption_key
|
|
167
|
+
keyring.bank_encryption&.key
|
|
38
168
|
end
|
|
39
169
|
|
|
40
|
-
def
|
|
41
|
-
|
|
170
|
+
def bank_encryption_version
|
|
171
|
+
keyring.bank_encryption&.version
|
|
42
172
|
end
|
|
43
173
|
|
|
44
|
-
def
|
|
45
|
-
|
|
174
|
+
def bank_authentication_key
|
|
175
|
+
keyring.bank_authentication&.key
|
|
46
176
|
end
|
|
47
177
|
|
|
48
|
-
def
|
|
49
|
-
|
|
178
|
+
def bank_authentication_version
|
|
179
|
+
keyring.bank_authentication&.version
|
|
50
180
|
end
|
|
51
181
|
|
|
52
|
-
def
|
|
53
|
-
|
|
182
|
+
def keys
|
|
183
|
+
user_signature = [keyring.user_signature, keyring.user_authentication,
|
|
184
|
+
keyring.user_encryption].each_with_object({}) do |signature, keys|
|
|
185
|
+
keys[signature.version] = signature.key if signature
|
|
186
|
+
end
|
|
187
|
+
bank_signature = [keyring.bank_authentication, keyring.bank_encryption].each_with_object({}) do |signature, keys|
|
|
188
|
+
keys["#{host_id.upcase}.#{signature.version}"] = signature.key if signature
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
user_signature.merge(bank_signature)
|
|
54
192
|
end
|
|
55
193
|
|
|
56
194
|
def name
|
|
57
|
-
@name ||=
|
|
195
|
+
@name ||= begin
|
|
196
|
+
self.HTD
|
|
197
|
+
@name
|
|
198
|
+
end
|
|
58
199
|
end
|
|
59
200
|
|
|
60
201
|
def iban
|
|
61
|
-
@iban ||=
|
|
202
|
+
@iban ||= begin
|
|
203
|
+
self.HTD
|
|
204
|
+
@iban
|
|
205
|
+
end
|
|
62
206
|
end
|
|
63
207
|
|
|
64
208
|
def bic
|
|
65
|
-
@bic ||=
|
|
209
|
+
@bic ||= begin
|
|
210
|
+
self.HTD
|
|
211
|
+
@bic
|
|
212
|
+
end
|
|
66
213
|
end
|
|
67
214
|
|
|
68
215
|
def order_types
|
|
69
|
-
@order_types ||=
|
|
216
|
+
@order_types ||= begin
|
|
217
|
+
self.HTD
|
|
218
|
+
@order_types
|
|
219
|
+
end
|
|
70
220
|
end
|
|
71
221
|
|
|
72
|
-
|
|
222
|
+
# Yields after the keys and certificates exist, so the block sees a usable client.
|
|
223
|
+
def self.setup(passphrase, url, host_id, user_id, partner_id, keysize = DEFAULT_KEY_SIZE, options = {})
|
|
224
|
+
options = options.dup
|
|
225
|
+
signature_version = options.delete(:signature_version) || Epics::Signature::A_VERSION_6
|
|
73
226
|
client = new(nil, passphrase, url, host_id, user_id, partner_id, options)
|
|
74
|
-
|
|
75
|
-
|
|
227
|
+
[signature_version, Epics::Signature::X_VERSION_2, Epics::Signature::E_VERSION_2].each do |version|
|
|
228
|
+
signature = case version
|
|
229
|
+
when Epics::Signature::A_VERSION_6
|
|
230
|
+
Epics::Signature.new(version, Epics::SignatureAlgorithm::RsaPss.new(OpenSSL::PKey::RSA.generate(keysize)))
|
|
231
|
+
when Epics::Signature::A_VERSION_5, Epics::Signature::X_VERSION_2, Epics::Signature::E_VERSION_2
|
|
232
|
+
Epics::Signature.new(version, Epics::SignatureAlgorithm::RsaPkcs1.new(OpenSSL::PKey::RSA.generate(keysize)))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
case signature.type
|
|
236
|
+
when Epics::Signature::TYPE_A
|
|
237
|
+
client.keyring.user_signature = signature
|
|
238
|
+
when Epics::Signature::TYPE_X
|
|
239
|
+
client.keyring.user_authentication = signature
|
|
240
|
+
when Epics::Signature::TYPE_E
|
|
241
|
+
client.keyring.user_encryption = signature
|
|
242
|
+
end
|
|
76
243
|
end
|
|
77
244
|
|
|
245
|
+
client.apply_certificates
|
|
246
|
+
client.generate_certificates if client.generate_certificates?
|
|
247
|
+
|
|
248
|
+
yield client if block_given?
|
|
78
249
|
client
|
|
79
250
|
end
|
|
80
251
|
|
|
@@ -91,20 +262,16 @@ class Epics::Client
|
|
|
91
262
|
path
|
|
92
263
|
end
|
|
93
264
|
|
|
94
|
-
def header_request
|
|
95
|
-
@header_request ||= Epics::HeaderRequest.new(self)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
265
|
def credit(document)
|
|
99
266
|
self.CCT(document)
|
|
100
267
|
end
|
|
101
268
|
|
|
102
269
|
def debit(document, type = :CDD)
|
|
103
|
-
|
|
270
|
+
public_send(type, document)
|
|
104
271
|
end
|
|
105
272
|
|
|
106
273
|
def statements(from, to, type = :STA)
|
|
107
|
-
|
|
274
|
+
public_send(type, from: from, to: to)
|
|
108
275
|
end
|
|
109
276
|
|
|
110
277
|
def HIA
|
|
@@ -117,14 +284,17 @@ class Epics::Client
|
|
|
117
284
|
|
|
118
285
|
def HEV
|
|
119
286
|
res = post(url, Epics::HEV.new(self).to_xml).body
|
|
120
|
-
res.doc.xpath(
|
|
287
|
+
res.doc.xpath('//xmlns:VersionNumber', xmlns: 'http://www.ebics.org/H000').each_with_object({}) do |node, versions|
|
|
121
288
|
versions[node['ProtocolVersion']] = node.content
|
|
122
289
|
end
|
|
123
290
|
end
|
|
124
291
|
|
|
125
292
|
def HPB
|
|
126
|
-
|
|
127
|
-
|
|
293
|
+
response_xml = download(Epics::HPB)
|
|
294
|
+
doc = Nokogiri::XML(response_xml)
|
|
295
|
+
|
|
296
|
+
doc.xpath('//xmlns:PubKeyValue', xmlns: urn_schema).each do |node|
|
|
297
|
+
signature_version = node.parent.last_element_child.content
|
|
128
298
|
|
|
129
299
|
modulus = Base64.decode64(node.at_xpath(".//*[local-name() = 'Modulus']").content)
|
|
130
300
|
exponent = Base64.decode64(node.at_xpath(".//*[local-name() = 'Exponent']").content)
|
|
@@ -134,67 +304,118 @@ class Epics::Client
|
|
|
134
304
|
sequence << OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(exponent, 2))
|
|
135
305
|
|
|
136
306
|
bank = OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
|
|
307
|
+
signature = Epics::Signature.new(
|
|
308
|
+
signature_version,
|
|
309
|
+
case signature_version
|
|
310
|
+
when Epics::Signature::E_VERSION_2, Epics::Signature::X_VERSION_2
|
|
311
|
+
Epics::SignatureAlgorithm::RsaPkcs1.new(bank)
|
|
312
|
+
end
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
case signature.type
|
|
316
|
+
when Epics::Signature::TYPE_E
|
|
317
|
+
keyring.bank_encryption = signature
|
|
318
|
+
when Epics::Signature::TYPE_X
|
|
319
|
+
keyring.bank_authentication = signature
|
|
320
|
+
end
|
|
321
|
+
rescue Epics::Signature::UnknownTypeError
|
|
322
|
+
rescue Epics::Signature::UnknownVersionError
|
|
323
|
+
end
|
|
137
324
|
|
|
138
|
-
|
|
325
|
+
doc.xpath('//ds:X509Certificate', xmlns: urn_schema,
|
|
326
|
+
ds: 'http://www.w3.org/2000/09/xmldsig#').each do |cert_node|
|
|
327
|
+
cert_pem = "-----BEGIN CERTIFICATE-----\n#{cert_node.content}\n-----END CERTIFICATE-----"
|
|
328
|
+
|
|
329
|
+
info_element = cert_node.parent&.parent
|
|
330
|
+
auth_version = info_element&.at_xpath('.//*[local-name() = "AuthenticationVersion"]')&.content
|
|
331
|
+
enc_version = info_element&.at_xpath('.//*[local-name() = "EncryptionVersion"]')&.content
|
|
332
|
+
version = auth_version || enc_version
|
|
333
|
+
|
|
334
|
+
next unless version
|
|
335
|
+
|
|
336
|
+
begin
|
|
337
|
+
cert = OpenSSL::X509::Certificate.new(cert_pem)
|
|
338
|
+
|
|
339
|
+
signature = Epics::Signature.new(
|
|
340
|
+
version,
|
|
341
|
+
Epics::SignatureAlgorithm::RsaPkcs1.new(cert.public_key)
|
|
342
|
+
)
|
|
343
|
+
signature.certificate = Epics::Crypt::X509.new(cert_pem)
|
|
344
|
+
|
|
345
|
+
case signature.type
|
|
346
|
+
when Epics::Signature::TYPE_E
|
|
347
|
+
keyring.bank_encryption = signature
|
|
348
|
+
when Epics::Signature::TYPE_X
|
|
349
|
+
keyring.bank_authentication = signature
|
|
350
|
+
end
|
|
351
|
+
rescue Epics::Signature::UnknownTypeError
|
|
352
|
+
rescue Epics::Signature::UnknownVersionError
|
|
353
|
+
rescue OpenSSL::OpenSSLError => e
|
|
354
|
+
raise Epics::InvalidCertificateError.new('the HPB response', e)
|
|
355
|
+
end
|
|
139
356
|
end
|
|
140
357
|
|
|
141
|
-
[
|
|
358
|
+
[bank_authentication_key, bank_encryption_key]
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def AZV(document, **options)
|
|
362
|
+
upload(Epics::AZV, document, **options)
|
|
142
363
|
end
|
|
143
364
|
|
|
144
|
-
def
|
|
145
|
-
upload(Epics::
|
|
365
|
+
def CD1(document, **options)
|
|
366
|
+
upload(Epics::CD1, document, **options)
|
|
146
367
|
end
|
|
147
368
|
|
|
148
|
-
def
|
|
149
|
-
upload(Epics::
|
|
369
|
+
def CDB(document, **options)
|
|
370
|
+
upload(Epics::CDB, document, **options)
|
|
150
371
|
end
|
|
151
372
|
|
|
152
|
-
def
|
|
153
|
-
upload(Epics::
|
|
373
|
+
def C2S(document, **options)
|
|
374
|
+
upload(Epics::C2S, document, **options)
|
|
154
375
|
end
|
|
155
376
|
|
|
156
|
-
def
|
|
157
|
-
upload(Epics::
|
|
377
|
+
def CDD(document, **options)
|
|
378
|
+
upload(Epics::CDD, document, **options)
|
|
158
379
|
end
|
|
159
380
|
|
|
160
|
-
def
|
|
161
|
-
upload(Epics::
|
|
381
|
+
def XE2(document, **options)
|
|
382
|
+
upload(Epics::XE2, document, **options)
|
|
162
383
|
end
|
|
163
384
|
|
|
164
|
-
def
|
|
165
|
-
upload(Epics::
|
|
385
|
+
def XE3(document, **options)
|
|
386
|
+
upload(Epics::XE3, document, **options)
|
|
166
387
|
end
|
|
167
388
|
|
|
168
|
-
def
|
|
169
|
-
upload(Epics::
|
|
389
|
+
def CDS(document, **options)
|
|
390
|
+
upload(Epics::CDS, document, **options)
|
|
170
391
|
end
|
|
171
392
|
|
|
172
|
-
def
|
|
173
|
-
upload(Epics::
|
|
393
|
+
def XDS(document, **options)
|
|
394
|
+
upload(Epics::XDS, document, **options)
|
|
174
395
|
end
|
|
175
396
|
|
|
176
|
-
def
|
|
177
|
-
upload(Epics::
|
|
397
|
+
def CCT(document, **options)
|
|
398
|
+
upload(Epics::CCT, document, **options)
|
|
178
399
|
end
|
|
179
400
|
|
|
180
|
-
def
|
|
181
|
-
upload(Epics::
|
|
401
|
+
def CIP(document, **options)
|
|
402
|
+
upload(Epics::CIP, document, **options)
|
|
182
403
|
end
|
|
183
404
|
|
|
184
|
-
def
|
|
185
|
-
upload(Epics::
|
|
405
|
+
def CCS(document, **options)
|
|
406
|
+
upload(Epics::CCS, document, **options)
|
|
186
407
|
end
|
|
187
408
|
|
|
188
|
-
def
|
|
189
|
-
upload(Epics::
|
|
409
|
+
def XCT(document, **options)
|
|
410
|
+
upload(Epics::XCT, document, **options)
|
|
190
411
|
end
|
|
191
412
|
|
|
192
|
-
def
|
|
193
|
-
upload(Epics::
|
|
413
|
+
def YCT(document, **options)
|
|
414
|
+
upload(Epics::YCT, document, **options)
|
|
194
415
|
end
|
|
195
416
|
|
|
196
|
-
def FUL(document)
|
|
197
|
-
upload(Epics::FUL, document)
|
|
417
|
+
def FUL(document, **options)
|
|
418
|
+
upload(Epics::FUL, document, **options)
|
|
198
419
|
end
|
|
199
420
|
|
|
200
421
|
def STA(from = nil, to = nil)
|
|
@@ -202,7 +423,7 @@ class Epics::Client
|
|
|
202
423
|
end
|
|
203
424
|
|
|
204
425
|
def FDL(format, from = nil, to = nil)
|
|
205
|
-
download(Epics::FDL, file_format: format, from: from, to: to
|
|
426
|
+
download(Epics::FDL, file_format: format, from: from, to: to)
|
|
206
427
|
end
|
|
207
428
|
|
|
208
429
|
def VMK(from = nil, to = nil)
|
|
@@ -221,48 +442,109 @@ class Epics::Client
|
|
|
221
442
|
download_and_unzip(Epics::BKA, from: from, to: to)
|
|
222
443
|
end
|
|
223
444
|
|
|
224
|
-
def C52(from, to)
|
|
225
|
-
download_and_unzip(Epics::C52, from: from, to: to
|
|
445
|
+
def C52(from, to, scope: nil, msg_name_version: nil, container_type: nil)
|
|
446
|
+
download_and_unzip(Epics::C52, from: from, to: to, scope: scope, msg_name_version: msg_name_version,
|
|
447
|
+
container_type: container_type)
|
|
226
448
|
end
|
|
227
449
|
|
|
228
|
-
def C53(from, to)
|
|
229
|
-
download_and_unzip(Epics::C53, from: from, to: to
|
|
450
|
+
def C53(from, to, scope: nil, msg_name_version: nil, container_type: nil)
|
|
451
|
+
download_and_unzip(Epics::C53, from: from, to: to, scope: scope, msg_name_version: msg_name_version,
|
|
452
|
+
container_type: container_type)
|
|
230
453
|
end
|
|
231
454
|
|
|
232
|
-
def C54(from, to)
|
|
233
|
-
download_and_unzip(Epics::C54, from: from, to: to
|
|
455
|
+
def C54(from, to, scope: nil, msg_name_version: nil, container_type: nil)
|
|
456
|
+
download_and_unzip(Epics::C54, from: from, to: to, scope: scope, msg_name_version: msg_name_version,
|
|
457
|
+
container_type: container_type)
|
|
234
458
|
end
|
|
235
459
|
|
|
236
460
|
def C5N(from, to)
|
|
237
461
|
download_and_unzip(Epics::C5N, from: from, to: to)
|
|
238
462
|
end
|
|
239
463
|
|
|
240
|
-
def Z01(from, to)
|
|
241
|
-
download_and_unzip(Epics::Z01, from: from, to: to
|
|
464
|
+
def Z01(from, to, scope: nil, service_option: nil, msg_name_version: nil, container_type: nil)
|
|
465
|
+
download_and_unzip(Epics::Z01, from: from, to: to, scope: scope, service_option: service_option,
|
|
466
|
+
msg_name_version: msg_name_version, container_type: container_type)
|
|
242
467
|
end
|
|
243
468
|
|
|
244
|
-
def Z52(from, to)
|
|
245
|
-
download_and_unzip(Epics::Z52, from: from, to: to
|
|
469
|
+
def Z52(from, to, scope: nil, service_option: nil, msg_name_version: nil, container_type: nil)
|
|
470
|
+
download_and_unzip(Epics::Z52, from: from, to: to, scope: scope, service_option: service_option,
|
|
471
|
+
msg_name_version: msg_name_version, container_type: container_type)
|
|
246
472
|
end
|
|
247
473
|
|
|
248
|
-
def Z53(from, to)
|
|
249
|
-
download_and_unzip(Epics::Z53, from: from, to: to
|
|
474
|
+
def Z53(from, to, scope: nil, service_option: nil, msg_name_version: nil, container_type: nil)
|
|
475
|
+
download_and_unzip(Epics::Z53, from: from, to: to, scope: scope, service_option: service_option,
|
|
476
|
+
msg_name_version: msg_name_version, container_type: container_type)
|
|
250
477
|
end
|
|
251
478
|
|
|
252
|
-
def Z54(from, to)
|
|
253
|
-
download_and_unzip(Epics::Z54, from: from, to: to
|
|
479
|
+
def Z54(from, to, scope: nil, service_option: nil, msg_name_version: nil, container_type: nil)
|
|
480
|
+
download_and_unzip(Epics::Z54, from: from, to: to, scope: scope, service_option: service_option,
|
|
481
|
+
msg_name_version: msg_name_version, container_type: container_type)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def XEK(from, to, scope: nil, service_option: nil, msg_name_version: nil, container_type: nil)
|
|
485
|
+
download_and_unzip(Epics::XEK, from: from, to: to, scope: scope, service_option: service_option,
|
|
486
|
+
msg_name_version: msg_name_version, container_type: container_type)
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def ZSR(from, to, scope: nil, service_option: nil, msg_name_version: nil, container_type: nil)
|
|
490
|
+
download_and_unzip(Epics::ZSR, from: from, to: to, scope: scope, service_option: service_option,
|
|
491
|
+
msg_name_version: msg_name_version, container_type: container_type)
|
|
254
492
|
end
|
|
255
493
|
|
|
256
494
|
def HAA
|
|
257
|
-
Nokogiri::XML(download(Epics::HAA))
|
|
495
|
+
doc = Nokogiri::XML(download(Epics::HAA))
|
|
496
|
+
ns = { xmlns: urn_schema }
|
|
497
|
+
|
|
498
|
+
case version
|
|
499
|
+
when Epics::Keyring::VERSION_24, Epics::Keyring::VERSION_25
|
|
500
|
+
doc.at_xpath('//xmlns:OrderTypes', ns).content.split(/\s/)
|
|
501
|
+
when Epics::Keyring::VERSION_30
|
|
502
|
+
doc.xpath('//xmlns:Service', ns).map do |service|
|
|
503
|
+
{
|
|
504
|
+
service_name: service.at_xpath('xmlns:ServiceName', ns)&.text,
|
|
505
|
+
scope: service.at_xpath('xmlns:Scope', ns)&.text,
|
|
506
|
+
service_option: service.at_xpath('xmlns:ServiceOption', ns)&.text,
|
|
507
|
+
container: service.at_xpath('xmlns:Container', ns)&.[]('containerType'),
|
|
508
|
+
msg_name: service.at_xpath('xmlns:MsgName', ns)&.text
|
|
509
|
+
}.compact
|
|
510
|
+
end
|
|
511
|
+
end
|
|
258
512
|
end
|
|
259
513
|
|
|
260
514
|
def HTD
|
|
261
515
|
Nokogiri::XML(download(Epics::HTD)).tap do |htd|
|
|
262
|
-
@iban
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
516
|
+
@iban ||= begin
|
|
517
|
+
htd.at_xpath("//xmlns:AccountNumber[@international='true']", xmlns: urn_schema).text
|
|
518
|
+
rescue StandardError
|
|
519
|
+
nil
|
|
520
|
+
end
|
|
521
|
+
@bic ||= begin
|
|
522
|
+
htd.at_xpath("//xmlns:BankCode[@international='true']", xmlns: urn_schema).text
|
|
523
|
+
rescue StandardError
|
|
524
|
+
nil
|
|
525
|
+
end
|
|
526
|
+
@name ||= begin
|
|
527
|
+
htd.at_xpath('//xmlns:Name', xmlns: urn_schema).text
|
|
528
|
+
rescue StandardError
|
|
529
|
+
nil
|
|
530
|
+
end
|
|
531
|
+
@order_types ||= case version
|
|
532
|
+
when Epics::Keyring::VERSION_24, Epics::Keyring::VERSION_25
|
|
533
|
+
htd.search('//xmlns:OrderTypes', xmlns: urn_schema).map do |o|
|
|
534
|
+
o.content.split(/\s/)
|
|
535
|
+
end.delete_if { |o| o == '' }.flatten
|
|
536
|
+
when Epics::Keyring::VERSION_30
|
|
537
|
+
htd.xpath('//xmlns:Service', xmlns: urn_schema).map do |service|
|
|
538
|
+
ns = { xmlns: urn_schema }
|
|
539
|
+
{
|
|
540
|
+
service_name: service.at_xpath('xmlns:ServiceName', ns)&.text,
|
|
541
|
+
scope: service.at_xpath('xmlns:Scope', ns)&.text,
|
|
542
|
+
service_option: service.at_xpath('xmlns:ServiceOption', ns)&.text,
|
|
543
|
+
container: service.at_xpath('xmlns:Container', ns)&.[]('containerType'),
|
|
544
|
+
msg_name: service.at_xpath('xmlns:MsgName', ns)&.text
|
|
545
|
+
}.compact
|
|
546
|
+
end
|
|
547
|
+
end
|
|
266
548
|
end.to_xml
|
|
267
549
|
end
|
|
268
550
|
|
|
@@ -289,32 +571,33 @@ class Epics::Client
|
|
|
289
571
|
def save_keys(path)
|
|
290
572
|
File.write(path, dump_keys)
|
|
291
573
|
end
|
|
292
|
-
|
|
293
|
-
def x_509_certificate(type)
|
|
294
|
-
content = x_509_certificates_content[type.to_sym]
|
|
295
|
-
return if content.nil? || content.empty?
|
|
296
|
-
Epics::X509Certificate.new(content)
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
def x_509_certificate_hash(type)
|
|
300
|
-
content = x_509_certificates_content[type.to_sym]
|
|
301
|
-
return if content.nil? || content.empty?
|
|
302
|
-
cert = OpenSSL::X509::Certificate.new(content)
|
|
303
|
-
Digest::SHA256.hexdigest(cert.to_der).upcase
|
|
304
|
-
end
|
|
305
574
|
|
|
306
575
|
private
|
|
307
576
|
|
|
308
|
-
def
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
577
|
+
def user_signatures
|
|
578
|
+
{
|
|
579
|
+
Epics::Signature::TYPE_A => keyring.user_signature,
|
|
580
|
+
Epics::Signature::TYPE_X => keyring.user_authentication,
|
|
581
|
+
Epics::Signature::TYPE_E => keyring.user_encryption,
|
|
582
|
+
}.compact
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def parse_certificate(content, option_name)
|
|
586
|
+
return if content.nil? || content.empty?
|
|
587
|
+
|
|
588
|
+
Epics::Crypt::X509.new(content)
|
|
589
|
+
rescue OpenSSL::X509::CertificateError => e
|
|
590
|
+
raise Epics::InvalidCertificateError.new(option_name, e)
|
|
591
|
+
end
|
|
312
592
|
|
|
313
|
-
|
|
593
|
+
def upload(order_type, document, **options)
|
|
594
|
+
order = order_type.new(self, document, **options)
|
|
595
|
+
session = post(url, order.to_xml).body
|
|
596
|
+
order.transaction_id = session.transaction_id
|
|
314
597
|
|
|
315
598
|
res = post(url, order.to_transfer_xml).body
|
|
316
599
|
|
|
317
|
-
|
|
600
|
+
[res.transaction_id, [res.order_id, session.order_id].detect { |id| id.to_s.chars.any? }]
|
|
318
601
|
end
|
|
319
602
|
|
|
320
603
|
def download(order_type, *args, **options)
|
|
@@ -322,9 +605,7 @@ class Epics::Client
|
|
|
322
605
|
res = post(url, document.to_xml).body
|
|
323
606
|
document.transaction_id = res.transaction_id
|
|
324
607
|
|
|
325
|
-
if res.segmented? && res.last_segment?
|
|
326
|
-
post(url, document.to_receipt_xml).body
|
|
327
|
-
end
|
|
608
|
+
post(url, document.to_receipt_xml).body if res.segmented? && res.last_segment?
|
|
328
609
|
|
|
329
610
|
res.order_data
|
|
330
611
|
end
|
|
@@ -338,22 +619,99 @@ class Epics::Client
|
|
|
338
619
|
end
|
|
339
620
|
|
|
340
621
|
def connection
|
|
341
|
-
@connection ||= Faraday.new(headers: { 'Content-Type' => 'text/xml', user_agent:
|
|
342
|
-
|
|
343
|
-
faraday.use Epics::ParseEbics, { client: self}
|
|
622
|
+
@connection ||= Faraday.new(headers: { 'Content-Type' => 'text/xml', user_agent: USER_AGENT },
|
|
623
|
+
ssl: { verify: verify_ssl? }) do |faraday|
|
|
624
|
+
faraday.use Epics::ParseEbics, { client: self }
|
|
344
625
|
# faraday.use MyAdapter
|
|
345
626
|
faraday.response :logger, ::Logger.new(STDOUT), bodies: true if debug_mode # log requests/response to STDOUT
|
|
346
627
|
end
|
|
347
628
|
end
|
|
348
629
|
|
|
349
630
|
def extract_keys
|
|
350
|
-
|
|
351
|
-
|
|
631
|
+
@unmapped_keys = {}
|
|
632
|
+
|
|
633
|
+
JSON.load(keys_content).each do |signature_version, value|
|
|
634
|
+
next unless value
|
|
635
|
+
|
|
636
|
+
entry_name = signature_version
|
|
637
|
+
|
|
638
|
+
# Handle new format: { "key" => "...", "cert" => "..." }
|
|
639
|
+
if value.is_a?(Hash)
|
|
640
|
+
key_pem = decrypt(value['key'])
|
|
641
|
+
cert_pem = value['cert'] ? decrypt(value['cert']) : nil
|
|
642
|
+
else
|
|
643
|
+
key_pem = decrypt(value)
|
|
644
|
+
cert_pem = nil
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
is_bank_key = signature_version.start_with?("#{host_id.upcase}.")
|
|
648
|
+
signature_version = signature_version.sub("#{host_id.upcase}.", '') if is_bank_key
|
|
649
|
+
|
|
650
|
+
signature = Epics::Signature.new(
|
|
651
|
+
signature_version,
|
|
652
|
+
case signature_version
|
|
653
|
+
when Epics::Signature::A_VERSION_6
|
|
654
|
+
Epics::SignatureAlgorithm::RsaPss.new(key_pem)
|
|
655
|
+
when Epics::Signature::A_VERSION_5, Epics::Signature::E_VERSION_2, Epics::Signature::X_VERSION_2
|
|
656
|
+
Epics::SignatureAlgorithm::RsaPkcs1.new(key_pem)
|
|
657
|
+
end
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
signature.certificate = Epics::Crypt::X509.new(cert_pem) if cert_pem
|
|
661
|
+
|
|
662
|
+
if is_bank_key
|
|
663
|
+
case signature.type
|
|
664
|
+
when Epics::Signature::TYPE_X
|
|
665
|
+
keyring.bank_authentication = signature
|
|
666
|
+
when Epics::Signature::TYPE_E
|
|
667
|
+
keyring.bank_encryption = signature
|
|
668
|
+
end
|
|
669
|
+
else
|
|
670
|
+
case signature.type
|
|
671
|
+
when Epics::Signature::TYPE_A
|
|
672
|
+
keyring.user_signature = signature
|
|
673
|
+
when Epics::Signature::TYPE_X
|
|
674
|
+
keyring.user_authentication = signature
|
|
675
|
+
when Epics::Signature::TYPE_E
|
|
676
|
+
keyring.user_encryption = signature
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
rescue Epics::Signature::UnknownTypeError, Epics::Signature::UnknownVersionError => e
|
|
680
|
+
# Kept verbatim so save_keys cannot drop it. A bank key whose prefix does not
|
|
681
|
+
# match host_id lands here, as does a key of a version this gem does not know.
|
|
682
|
+
@unmapped_keys[entry_name] = value
|
|
683
|
+
warn "[epics] WARNING: keeping #{entry_name.inspect} in the key file unchanged: #{e.message}"
|
|
352
684
|
end
|
|
353
685
|
end
|
|
354
686
|
|
|
355
687
|
def dump_keys
|
|
356
|
-
|
|
688
|
+
# Entries extract_keys could not place are carried through, so saving never
|
|
689
|
+
# discards part of the key file.
|
|
690
|
+
data = (@unmapped_keys || {}).dup
|
|
691
|
+
|
|
692
|
+
[keyring.user_signature, keyring.user_authentication,
|
|
693
|
+
keyring.user_encryption].each do |sig|
|
|
694
|
+
next if !sig
|
|
695
|
+
|
|
696
|
+
data[sig.version] = dump_signature(sig)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
[keyring.bank_authentication, keyring.bank_encryption].each do |sig|
|
|
700
|
+
next if !sig
|
|
701
|
+
|
|
702
|
+
data["#{host_id.upcase}.#{sig.version}"] = dump_signature(sig)
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
JSON.pretty_generate(data, JSON.dump_default_options)
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
# Keys without a certificate keep the bare-string form so key files stay readable by
|
|
709
|
+
# older versions of the gem.
|
|
710
|
+
def dump_signature(signature)
|
|
711
|
+
key = encrypt(signature.key.key.to_pem)
|
|
712
|
+
return key if !signature.certificate
|
|
713
|
+
|
|
714
|
+
{ 'key' => key, 'cert' => encrypt(signature.certificate.to_pem) }
|
|
357
715
|
end
|
|
358
716
|
|
|
359
717
|
def new_cipher
|
|
@@ -365,17 +723,23 @@ class Epics::Client
|
|
|
365
723
|
def encrypt(data)
|
|
366
724
|
salt = OpenSSL::Random.random_bytes(8)
|
|
367
725
|
|
|
368
|
-
cipher = setup_cipher(:encrypt,
|
|
726
|
+
cipher = setup_cipher(:encrypt, passphrase, salt)
|
|
369
727
|
Base64.strict_encode64([salt, cipher.update(data) + cipher.final].join)
|
|
370
728
|
end
|
|
371
729
|
|
|
730
|
+
# Every value in a key file is a PEM. A wrong passphrase normally fails the padding
|
|
731
|
+
# check, but about one time in 256 it decrypts to garbage that happens to be padded
|
|
732
|
+
# correctly; without this check that surfaces later as an unrelated key parse error.
|
|
372
733
|
def decrypt(data)
|
|
373
734
|
data = Base64.strict_decode64(data)
|
|
374
735
|
salt = data[0..7]
|
|
375
736
|
data = data[8..-1]
|
|
376
737
|
|
|
377
|
-
cipher = setup_cipher(:decrypt,
|
|
378
|
-
cipher.update(data) + cipher.final
|
|
738
|
+
cipher = setup_cipher(:decrypt, passphrase, salt)
|
|
739
|
+
plaintext = cipher.update(data) + cipher.final
|
|
740
|
+
raise OpenSSL::Cipher::CipherError, 'cipher final failed: wrong passphrase' if !plaintext.start_with?('-----BEGIN')
|
|
741
|
+
|
|
742
|
+
plaintext
|
|
379
743
|
end
|
|
380
744
|
|
|
381
745
|
def setup_cipher(method, passphrase, salt)
|