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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.semaphore/semaphore.yml +17 -8
- data/CHANGELOG.md +97 -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 +491 -123
- 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 +6 -0
- 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 +69 -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 +63 -0
- 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 +200 -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
|
|
34
152
|
end
|
|
35
153
|
|
|
36
|
-
def
|
|
37
|
-
|
|
154
|
+
def signature_key
|
|
155
|
+
keyring.user_signature&.key
|
|
38
156
|
end
|
|
39
157
|
|
|
40
|
-
def
|
|
41
|
-
|
|
158
|
+
def authentication_version
|
|
159
|
+
keyring.user_authentication&.version
|
|
42
160
|
end
|
|
43
161
|
|
|
44
|
-
def
|
|
45
|
-
|
|
162
|
+
def authentication_key
|
|
163
|
+
keyring.user_authentication&.key
|
|
46
164
|
end
|
|
47
165
|
|
|
48
|
-
def
|
|
49
|
-
|
|
166
|
+
def bank_encryption_key
|
|
167
|
+
keyring.bank_encryption&.key
|
|
50
168
|
end
|
|
51
169
|
|
|
52
|
-
def
|
|
53
|
-
|
|
170
|
+
def bank_encryption_version
|
|
171
|
+
keyring.bank_encryption&.version
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def bank_authentication_key
|
|
175
|
+
keyring.bank_authentication&.key
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def bank_authentication_version
|
|
179
|
+
keyring.bank_authentication&.version
|
|
180
|
+
end
|
|
181
|
+
|
|
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,63 +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)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def CD1(document, **options)
|
|
366
|
+
upload(Epics::CD1, document, **options)
|
|
142
367
|
end
|
|
143
368
|
|
|
144
|
-
def
|
|
145
|
-
upload(Epics::
|
|
369
|
+
def CDB(document, **options)
|
|
370
|
+
upload(Epics::CDB, document, **options)
|
|
146
371
|
end
|
|
147
372
|
|
|
148
|
-
def
|
|
149
|
-
upload(Epics::
|
|
373
|
+
def C2S(document, **options)
|
|
374
|
+
upload(Epics::C2S, document, **options)
|
|
150
375
|
end
|
|
151
376
|
|
|
152
|
-
def
|
|
153
|
-
upload(Epics::
|
|
377
|
+
def CDD(document, **options)
|
|
378
|
+
upload(Epics::CDD, document, **options)
|
|
154
379
|
end
|
|
155
380
|
|
|
156
|
-
def
|
|
157
|
-
upload(Epics::
|
|
381
|
+
def XE2(document, **options)
|
|
382
|
+
upload(Epics::XE2, document, **options)
|
|
158
383
|
end
|
|
159
384
|
|
|
160
|
-
def
|
|
161
|
-
upload(Epics::
|
|
385
|
+
def XE3(document, **options)
|
|
386
|
+
upload(Epics::XE3, document, **options)
|
|
162
387
|
end
|
|
163
388
|
|
|
164
|
-
def
|
|
165
|
-
upload(Epics::
|
|
389
|
+
def CDS(document, **options)
|
|
390
|
+
upload(Epics::CDS, document, **options)
|
|
166
391
|
end
|
|
167
392
|
|
|
168
|
-
def
|
|
169
|
-
upload(Epics::
|
|
393
|
+
def XDS(document, **options)
|
|
394
|
+
upload(Epics::XDS, document, **options)
|
|
170
395
|
end
|
|
171
396
|
|
|
172
|
-
def
|
|
173
|
-
upload(Epics::
|
|
397
|
+
def CCT(document, **options)
|
|
398
|
+
upload(Epics::CCT, document, **options)
|
|
174
399
|
end
|
|
175
400
|
|
|
176
|
-
def
|
|
177
|
-
upload(Epics::
|
|
401
|
+
def CIP(document, **options)
|
|
402
|
+
upload(Epics::CIP, document, **options)
|
|
178
403
|
end
|
|
179
404
|
|
|
180
|
-
def
|
|
181
|
-
upload(Epics::
|
|
405
|
+
def CCS(document, **options)
|
|
406
|
+
upload(Epics::CCS, document, **options)
|
|
182
407
|
end
|
|
183
408
|
|
|
184
|
-
def
|
|
185
|
-
upload(Epics::
|
|
409
|
+
def XCT(document, **options)
|
|
410
|
+
upload(Epics::XCT, document, **options)
|
|
186
411
|
end
|
|
187
412
|
|
|
188
|
-
def
|
|
189
|
-
upload(Epics::
|
|
413
|
+
def YCT(document, **options)
|
|
414
|
+
upload(Epics::YCT, document, **options)
|
|
190
415
|
end
|
|
191
416
|
|
|
192
|
-
def
|
|
193
|
-
upload(Epics::
|
|
417
|
+
def FUL(document, **options)
|
|
418
|
+
upload(Epics::FUL, document, **options)
|
|
194
419
|
end
|
|
195
420
|
|
|
196
421
|
def STA(from = nil, to = nil)
|
|
@@ -198,7 +423,7 @@ class Epics::Client
|
|
|
198
423
|
end
|
|
199
424
|
|
|
200
425
|
def FDL(format, from = nil, to = nil)
|
|
201
|
-
download(Epics::FDL, file_format: format, from: from, to: to
|
|
426
|
+
download(Epics::FDL, file_format: format, from: from, to: to)
|
|
202
427
|
end
|
|
203
428
|
|
|
204
429
|
def VMK(from = nil, to = nil)
|
|
@@ -217,48 +442,109 @@ class Epics::Client
|
|
|
217
442
|
download_and_unzip(Epics::BKA, from: from, to: to)
|
|
218
443
|
end
|
|
219
444
|
|
|
220
|
-
def C52(from, to)
|
|
221
|
-
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)
|
|
222
448
|
end
|
|
223
449
|
|
|
224
|
-
def C53(from, to)
|
|
225
|
-
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)
|
|
226
453
|
end
|
|
227
454
|
|
|
228
|
-
def C54(from, to)
|
|
229
|
-
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)
|
|
230
458
|
end
|
|
231
459
|
|
|
232
460
|
def C5N(from, to)
|
|
233
461
|
download_and_unzip(Epics::C5N, from: from, to: to)
|
|
234
462
|
end
|
|
235
463
|
|
|
236
|
-
def Z01(from, to)
|
|
237
|
-
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)
|
|
238
467
|
end
|
|
239
468
|
|
|
240
|
-
def Z52(from, to)
|
|
241
|
-
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)
|
|
242
472
|
end
|
|
243
473
|
|
|
244
|
-
def Z53(from, to)
|
|
245
|
-
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)
|
|
246
477
|
end
|
|
247
478
|
|
|
248
|
-
def Z54(from, to)
|
|
249
|
-
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)
|
|
250
492
|
end
|
|
251
493
|
|
|
252
494
|
def HAA
|
|
253
|
-
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
|
|
254
512
|
end
|
|
255
513
|
|
|
256
514
|
def HTD
|
|
257
515
|
Nokogiri::XML(download(Epics::HTD)).tap do |htd|
|
|
258
|
-
@iban
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
262
548
|
end.to_xml
|
|
263
549
|
end
|
|
264
550
|
|
|
@@ -285,32 +571,33 @@ class Epics::Client
|
|
|
285
571
|
def save_keys(path)
|
|
286
572
|
File.write(path, dump_keys)
|
|
287
573
|
end
|
|
288
|
-
|
|
289
|
-
def x_509_certificate(type)
|
|
290
|
-
content = x_509_certificates_content[type.to_sym]
|
|
291
|
-
return if content.nil? || content.empty?
|
|
292
|
-
Epics::X509Certificate.new(content)
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
def x_509_certificate_hash(type)
|
|
296
|
-
content = x_509_certificates_content[type.to_sym]
|
|
297
|
-
return if content.nil? || content.empty?
|
|
298
|
-
cert = OpenSSL::X509::Certificate.new(content)
|
|
299
|
-
Digest::SHA256.hexdigest(cert.to_der).upcase
|
|
300
|
-
end
|
|
301
574
|
|
|
302
575
|
private
|
|
303
576
|
|
|
304
|
-
def
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
|
308
592
|
|
|
309
|
-
|
|
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
|
|
310
597
|
|
|
311
598
|
res = post(url, order.to_transfer_xml).body
|
|
312
599
|
|
|
313
|
-
|
|
600
|
+
[res.transaction_id, [res.order_id, session.order_id].detect { |id| id.to_s.chars.any? }]
|
|
314
601
|
end
|
|
315
602
|
|
|
316
603
|
def download(order_type, *args, **options)
|
|
@@ -318,9 +605,7 @@ class Epics::Client
|
|
|
318
605
|
res = post(url, document.to_xml).body
|
|
319
606
|
document.transaction_id = res.transaction_id
|
|
320
607
|
|
|
321
|
-
if res.segmented? && res.last_segment?
|
|
322
|
-
post(url, document.to_receipt_xml).body
|
|
323
|
-
end
|
|
608
|
+
post(url, document.to_receipt_xml).body if res.segmented? && res.last_segment?
|
|
324
609
|
|
|
325
610
|
res.order_data
|
|
326
611
|
end
|
|
@@ -334,22 +619,99 @@ class Epics::Client
|
|
|
334
619
|
end
|
|
335
620
|
|
|
336
621
|
def connection
|
|
337
|
-
@connection ||= Faraday.new(headers: { 'Content-Type' => 'text/xml', user_agent:
|
|
338
|
-
|
|
339
|
-
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 }
|
|
340
625
|
# faraday.use MyAdapter
|
|
341
626
|
faraday.response :logger, ::Logger.new(STDOUT), bodies: true if debug_mode # log requests/response to STDOUT
|
|
342
627
|
end
|
|
343
628
|
end
|
|
344
629
|
|
|
345
630
|
def extract_keys
|
|
346
|
-
|
|
347
|
-
|
|
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}"
|
|
348
684
|
end
|
|
349
685
|
end
|
|
350
686
|
|
|
351
687
|
def dump_keys
|
|
352
|
-
|
|
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) }
|
|
353
715
|
end
|
|
354
716
|
|
|
355
717
|
def new_cipher
|
|
@@ -361,17 +723,23 @@ class Epics::Client
|
|
|
361
723
|
def encrypt(data)
|
|
362
724
|
salt = OpenSSL::Random.random_bytes(8)
|
|
363
725
|
|
|
364
|
-
cipher = setup_cipher(:encrypt,
|
|
726
|
+
cipher = setup_cipher(:encrypt, passphrase, salt)
|
|
365
727
|
Base64.strict_encode64([salt, cipher.update(data) + cipher.final].join)
|
|
366
728
|
end
|
|
367
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.
|
|
368
733
|
def decrypt(data)
|
|
369
734
|
data = Base64.strict_decode64(data)
|
|
370
735
|
salt = data[0..7]
|
|
371
736
|
data = data[8..-1]
|
|
372
737
|
|
|
373
|
-
cipher = setup_cipher(:decrypt,
|
|
374
|
-
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
|
|
375
743
|
end
|
|
376
744
|
|
|
377
745
|
def setup_cipher(method, passphrase, salt)
|