sepafm 0.1.3 → 0.1.4
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/lib/sepa/application_request.rb +1 -1
- data/lib/sepa/application_response.rb +15 -16
- data/lib/sepa/attribute_checks.rb +18 -25
- data/lib/sepa/banks/danske/danske_response.rb +65 -8
- data/lib/sepa/banks/danske/soap_danske.rb +1 -1
- data/lib/sepa/banks/nordea/nordea_response.rb +11 -0
- data/lib/sepa/certificates/danske_root_certificate.cer +25 -0
- data/lib/sepa/client.rb +35 -29
- data/lib/sepa/error_messages.rb +11 -0
- data/lib/sepa/response.rb +42 -20
- data/lib/sepa/soap_builder.rb +20 -38
- data/lib/sepa/utilities.rb +36 -11
- data/lib/sepa/version.rb +1 -1
- data/lib/sepafm.rb +10 -1
- data/readme.md +64 -28
- data/test/sepa/banks/danske/danske_cert_response_test.rb +41 -4
- data/test/sepa/banks/danske/danske_cert_soap_builder_test.rb +1 -1
- data/test/sepa/banks/danske/danske_generic_soap_builder_test.rb +6 -17
- data/test/sepa/banks/danske/danske_response_test.rb +97 -0
- data/test/sepa/banks/danske/keys/bank_encryption_cert.pem +17 -17
- data/test/sepa/banks/danske/keys/bank_signing_cert.pem +17 -17
- data/test/sepa/banks/danske/responses/create_cert.xml +14 -14
- data/test/sepa/banks/danske/responses/download_file_list.xml +42 -0
- data/test/sepa/banks/danske/responses/get_bank_cert.xml +14 -36
- data/test/sepa/banks/danske/responses/get_bank_certificate_not_ok.xml +2 -0
- data/test/sepa/banks/nordea/nordea_application_request_test.rb +3 -5
- data/test/sepa/banks/nordea/nordea_application_response_test.rb +25 -30
- data/test/sepa/banks/nordea/nordea_generic_soap_builder_test.rb +2 -2
- data/test/sepa/banks/nordea/nordea_response_test.rb +83 -17
- data/test/sepa/banks/nordea/responses/df_ktl.xml +20 -44
- data/test/sepa/banks/nordea/responses/dfl.xml +7 -6
- data/test/sepa/banks/nordea/responses/download_file_list_no_content.xml +21 -0
- data/test/sepa/banks/nordea/responses/gc.xml +21 -49
- data/test/sepa/banks/nordea/responses/invalid/body_altered.xml +21 -0
- data/test/sepa/banks/nordea/responses/invalid/timestamp_altered.xml +21 -0
- data/test/sepa/banks/nordea/responses/not_ok_response_code.xml +21 -0
- data/test/sepa/banks/nordea/responses/uf.xml +7 -6
- data/test/sepa/client_test.rb +134 -35
- data/test/sepa/fixtures.rb +8 -8
- data/test/sepa/sepa_test.rb +1 -1
- data/test/test_helper.rb +5 -1
- metadata +18 -6
- data/test/sepa/banks/nordea/responses/gbc.xml +0 -15
- /data/{test/sepa/banks/nordea/keys/root_cert.cer → lib/sepa/certificates/nordea_root_certificate.cer} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b12a4088afb4ab54adbbab5c077ffabed2637060
|
4
|
+
data.tar.gz: 677279058018c696a4f5e6d0b2e47a82527213ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ed8c28c79a852890cfaf17c34180cbfe2291800ff742215eb0e80c52b3ccb7ad00496499c3cc83b68407f1ce46febfd0aab4a4659d5a4e4d6392230020a54d0
|
7
|
+
data.tar.gz: ac504dbcb51ab54f63fdc72b991fbfb07aea8e5529fd280b1ef4d686faa3641751184910a988f38aab4bbc0e9ccfe8e825bfdfdb8a8c7365dc45ac04ec9c446f
|
@@ -154,7 +154,7 @@ module Sepa
|
|
154
154
|
add_node_to_root(signature_node)
|
155
155
|
add_value_to_signature('DigestValue', digest)
|
156
156
|
add_value_to_signature('SignatureValue', calculate_signature)
|
157
|
-
add_value_to_signature('X509Certificate', format_cert(@
|
157
|
+
add_value_to_signature('X509Certificate', format_cert(@own_signing_certificate))
|
158
158
|
end
|
159
159
|
|
160
160
|
def add_target_id_after(node)
|
@@ -6,10 +6,10 @@ module Sepa
|
|
6
6
|
attr_reader :xml
|
7
7
|
|
8
8
|
validate :response_must_validate_against_schema
|
9
|
-
validate :validate_document_format
|
10
9
|
|
11
|
-
def initialize(app_resp)
|
10
|
+
def initialize(app_resp, bank)
|
12
11
|
@xml = app_resp
|
12
|
+
@bank = bank
|
13
13
|
end
|
14
14
|
|
15
15
|
def doc
|
@@ -33,14 +33,7 @@ module Sepa
|
|
33
33
|
|
34
34
|
# Checks that the signature is signed with the private key of the certificate's public key.
|
35
35
|
def signature_is_valid?
|
36
|
-
|
37
|
-
node = node.canonicalize
|
38
|
-
|
39
|
-
signature = doc.at('xmlns|SignatureValue', 'xmlns' => DSIG).content
|
40
|
-
signature = decode(signature)
|
41
|
-
|
42
|
-
# Return true or false
|
43
|
-
certificate.public_key.verify(OpenSSL::Digest::SHA1.new, signature, node)
|
36
|
+
validate_signature(doc, certificate, :normal)
|
44
37
|
end
|
45
38
|
|
46
39
|
def to_s
|
@@ -51,13 +44,19 @@ module Sepa
|
|
51
44
|
extract_cert(doc, 'X509Certificate', DSIG)
|
52
45
|
end
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
47
|
+
def certificate_is_trusted?
|
48
|
+
root_certificate =
|
49
|
+
case @bank
|
50
|
+
when :nordea
|
51
|
+
NORDEA_ROOT_CERTIFICATE
|
52
|
+
when :danske
|
53
|
+
DANSKE_ROOT_CERTIFICATE
|
59
54
|
end
|
60
|
-
|
55
|
+
|
56
|
+
verify_certificate_against_root_certificate(certificate, root_certificate)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
61
60
|
|
62
61
|
def response_must_validate_against_schema
|
63
62
|
check_validity_against_schema(doc, 'application_response.xsd')
|
@@ -28,14 +28,14 @@ module Sepa
|
|
28
28
|
end
|
29
29
|
|
30
30
|
begin
|
31
|
-
|
31
|
+
x509_certificate own_signing_certificate
|
32
32
|
rescue
|
33
|
-
errors.add(:
|
33
|
+
errors.add(:own_signing_certificate, "Invalid signing certificate")
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
37
|
def check_signing_csr
|
38
|
-
return unless
|
38
|
+
return unless [:get_certificate, :create_certificate].include? command
|
39
39
|
|
40
40
|
unless cert_request_valid?(signing_csr)
|
41
41
|
errors.add(:signing_csr, SIGNING_CERT_REQUEST_ERROR_MESSAGE)
|
@@ -50,22 +50,10 @@ module Sepa
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
def check_wsdl
|
54
|
-
return unless wsdl.present?
|
55
|
-
|
56
|
-
xsd = Nokogiri::XML::Schema(File.read(SCHEMA_FILE))
|
57
|
-
wsdl_file = File.read(wsdl)
|
58
|
-
xml = Nokogiri::XML(wsdl_file)
|
59
|
-
|
60
|
-
unless xsd.valid?(xml)
|
61
|
-
errors.add(:wsdl, "Invalid wsdl file")
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
53
|
def check_file_type
|
66
54
|
return unless [:upload_file, :download_file_list, :download_file].include? command
|
67
55
|
|
68
|
-
|
56
|
+
unless file_type && file_type.respond_to?(:size) && file_type.size < 35
|
69
57
|
errors.add(:file_type, FILE_TYPE_ERROR_MESSAGE)
|
70
58
|
end
|
71
59
|
end
|
@@ -83,7 +71,7 @@ module Sepa
|
|
83
71
|
end
|
84
72
|
|
85
73
|
def check_presence_and_length(attribute, length, error_message)
|
86
|
-
|
74
|
+
unless send(attribute) && send(attribute).respond_to?(:size) && send(attribute).size < length
|
87
75
|
errors.add(attribute, error_message)
|
88
76
|
end
|
89
77
|
end
|
@@ -91,13 +79,13 @@ module Sepa
|
|
91
79
|
def check_content
|
92
80
|
return unless command == :upload_file
|
93
81
|
|
94
|
-
errors.add(:content, CONTENT_ERROR_MESSAGE)
|
82
|
+
errors.add(:content, CONTENT_ERROR_MESSAGE) unless content && content.respond_to?(:length)
|
95
83
|
end
|
96
84
|
|
97
85
|
def check_pin
|
98
|
-
return unless
|
86
|
+
return unless [:create_certificate, :get_certificate].include? command
|
99
87
|
|
100
|
-
check_presence_and_length(:pin,
|
88
|
+
check_presence_and_length(:pin, 20, PIN_ERROR_MESSAGE)
|
101
89
|
end
|
102
90
|
|
103
91
|
def check_environment
|
@@ -109,7 +97,7 @@ module Sepa
|
|
109
97
|
end
|
110
98
|
|
111
99
|
def check_customer_id
|
112
|
-
unless customer_id && customer_id.length.between?(1, 16)
|
100
|
+
unless customer_id && customer_id.respond_to?(:length) && customer_id.length.between?(1, 16)
|
113
101
|
errors.add(:customer_id, CUSTOMER_ID_ERROR_MESSAGE)
|
114
102
|
end
|
115
103
|
end
|
@@ -118,7 +106,14 @@ module Sepa
|
|
118
106
|
return unless bank == :danske
|
119
107
|
return if command == :get_bank_certificate
|
120
108
|
|
121
|
-
|
109
|
+
unless bank_encryption_certificate
|
110
|
+
return errors.add(:bank_encryption_certificate, ENCRYPTION_CERT_ERROR_MESSAGE)
|
111
|
+
end
|
112
|
+
|
113
|
+
x509_certificate bank_encryption_certificate
|
114
|
+
|
115
|
+
rescue
|
116
|
+
errors.add(:bank_encryption_certificate, ENCRYPTION_CERT_ERROR_MESSAGE)
|
122
117
|
end
|
123
118
|
|
124
119
|
def check_status
|
@@ -132,9 +127,7 @@ module Sepa
|
|
132
127
|
def check_file_reference
|
133
128
|
return unless command == :download_file
|
134
129
|
|
135
|
-
|
136
|
-
errors.add :file_reference, FILE_REFERENCE_ERROR_MESSAGE
|
137
|
-
end
|
130
|
+
check_presence_and_length :file_reference, 33, FILE_REFERENCE_ERROR_MESSAGE
|
138
131
|
end
|
139
132
|
|
140
133
|
def check_encryption_private_key
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Sepa
|
2
2
|
class DanskeResponse < Response
|
3
3
|
|
4
|
+
validate :valid_get_bank_certificate_response
|
5
|
+
validate :can_be_decrypted_with_given_key
|
6
|
+
|
4
7
|
def application_response
|
5
8
|
@application_response ||= decrypt_application_response
|
6
9
|
end
|
@@ -46,27 +49,40 @@ module Sepa
|
|
46
49
|
@certificate ||= begin
|
47
50
|
extract_cert(doc, 'X509Certificate', DSIG)
|
48
51
|
end
|
52
|
+
else
|
53
|
+
super
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
57
|
+
def response_code
|
58
|
+
return super unless [:get_bank_certificate, :create_certificate].include? @command
|
59
|
+
|
60
|
+
node = doc.at('xmlns|ReturnCode', xmlns: DANSKE_PKI)
|
61
|
+
node.content if node
|
62
|
+
end
|
63
|
+
|
64
|
+
def certificate_is_trusted?
|
65
|
+
return true if @command == :get_bank_certificate
|
66
|
+
|
67
|
+
verify_certificate_against_root_certificate(certificate, DANSKE_ROOT_CERTIFICATE)
|
68
|
+
end
|
69
|
+
|
52
70
|
private
|
53
71
|
|
54
72
|
def find_node_by_uri(uri)
|
55
|
-
|
73
|
+
return super unless [:get_bank_certificate, :create_certificate].include? @command
|
74
|
+
|
75
|
+
node = doc.at("[xml|id='#{uri}']").clone
|
56
76
|
node.at('xmlns|Signature', xmlns: DSIG).remove
|
57
77
|
node
|
58
78
|
end
|
59
79
|
|
60
80
|
def decrypt_application_response
|
61
|
-
|
62
|
-
encrypted_application_response = xml_doc encrypted_application_response
|
63
|
-
enc_key = encrypted_application_response.css('CipherValue', 'xmlns' => XMLENC)[0].content
|
64
|
-
enc_key = decode enc_key
|
65
|
-
key = @encryption_private_key.private_decrypt(enc_key)
|
81
|
+
key = decrypt_embedded_key
|
66
82
|
|
67
83
|
encypted_data = encrypted_application_response
|
68
|
-
|
69
|
-
|
84
|
+
.css('CipherValue', 'xmlns' => XMLENC)[1]
|
85
|
+
.content
|
70
86
|
|
71
87
|
encypted_data = decode encypted_data
|
72
88
|
iv = encypted_data[0, 8]
|
@@ -80,5 +96,46 @@ module Sepa
|
|
80
96
|
decipher.update(encypted_data) + decipher.final
|
81
97
|
end
|
82
98
|
|
99
|
+
def valid_get_bank_certificate_response
|
100
|
+
return unless @command == :get_bank_certificate
|
101
|
+
|
102
|
+
if doc.at('xmlns|PKIFactoryServiceFault', xmlns: DANSKE_PKIF)
|
103
|
+
errors.add(:base, "Did not get a proper response when trying to get bank's certificates")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def encrypted_application_response
|
108
|
+
@encrypted_application_response ||= begin
|
109
|
+
encrypted_application_response = extract_application_response(BXD)
|
110
|
+
xml_doc encrypted_application_response
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def can_be_decrypted_with_given_key
|
115
|
+
return if [:get_bank_certificate, :create_certificate].include? @command
|
116
|
+
return unless encrypted_application_response.css('CipherValue', 'xmlns' => XMLENC)[0]
|
117
|
+
|
118
|
+
unless decrypt_embedded_key
|
119
|
+
errors.add(:encryption_private_key, DECRYPTION_ERROR_MESSAGE)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def decrypt_embedded_key
|
124
|
+
enc_key = encrypted_application_response.css('CipherValue', 'xmlns' => XMLENC)[0].content
|
125
|
+
enc_key = decode enc_key
|
126
|
+
@encryption_private_key.private_decrypt(enc_key)
|
127
|
+
|
128
|
+
rescue OpenSSL::PKey::RSAError
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def verify_signature
|
133
|
+
super unless [:get_bank_certificate, :create_certificate].include? @command
|
134
|
+
end
|
135
|
+
|
136
|
+
def validate_hashes
|
137
|
+
super unless [:get_bank_certificate, :create_certificate].include? @command
|
138
|
+
end
|
139
|
+
|
83
140
|
end
|
84
141
|
end
|
@@ -15,7 +15,7 @@ module Sepa
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def encrypt_application_request
|
18
|
-
encryption_certificate =
|
18
|
+
encryption_certificate = x509_certificate(@bank_encryption_certificate)
|
19
19
|
encryption_public_key = encryption_certificate.public_key
|
20
20
|
encryption_certificate = format_cert(encryption_certificate)
|
21
21
|
encrypted_application_request, key = encrypt_ar
|
@@ -14,5 +14,16 @@ module Sepa
|
|
14
14
|
cert.to_s
|
15
15
|
end
|
16
16
|
|
17
|
+
def response_code
|
18
|
+
return super unless command == :get_certificate
|
19
|
+
|
20
|
+
node = doc.at('xmlns|ResponseCode', xmlns: NORDEA_PKI)
|
21
|
+
node.content if node
|
22
|
+
end
|
23
|
+
|
24
|
+
def certificate_is_trusted?
|
25
|
+
verify_certificate_against_root_certificate(certificate, NORDEA_ROOT_CERTIFICATE)
|
26
|
+
end
|
27
|
+
|
17
28
|
end
|
18
29
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIEPzCCAyegAwIBAgIEQjoxcjANBgkqhkiG9w0BAQsFADCBmDEQMA4GA1UEAxMH
|
3
|
+
REJHUk9PVDELMAkGA1UEBhMCREsxEzARBgNVBAcTCkNvcGVuaGFnZW4xEDAOBgNV
|
4
|
+
BAgTB0Rlbm1hcmsxGjAYBgNVBAoTEURhbnNrZSBCYW5rIEdyb3VwMRowGAYDVQQL
|
5
|
+
ExFEYW5za2UgQmFuayBHcm91cDEYMBYGA1UEBRMPNjExMjYyMjgxMTEwMDAyMB4X
|
6
|
+
DTEwMTAyNzAwMDAwMFoXDTIwMTAyNzAwMDAwMFowgZgxEDAOBgNVBAMTB0RCR1JP
|
7
|
+
T1QxCzAJBgNVBAYTAkRLMRMwEQYDVQQHEwpDb3BlbmhhZ2VuMRAwDgYDVQQIEwdE
|
8
|
+
ZW5tYXJrMRowGAYDVQQKExFEYW5za2UgQmFuayBHcm91cDEaMBgGA1UECxMRRGFu
|
9
|
+
c2tlIEJhbmsgR3JvdXAxGDAWBgNVBAUTDzYxMTI2MjI4MTExMDAwMjCCASAwDQYJ
|
10
|
+
KoZIhvcNAQEBBQADggENADCCAQgCggEBAKWRtTRCXNEn5Hj+tA0vVg8VKUi/HnFg
|
11
|
+
ioZW/eyaF4gWvR4PNXXJJOS31VNHnb2SQHPLt3ac+5icH7vLu/OtS5rvnDiDFMg+
|
12
|
+
TomVDrur6RtlsZNLnihZiaSaooI49+ERTz6vcCjST7xbfhmC03LUhE8eBKI1U70c
|
13
|
+
x/lQ55UQKZvIAIbCVaZEks95VS4uJpwnU4M8glNIVGSvJhIUj/LIkSIcqBiryq/t
|
14
|
+
9FRVtRl1gVhwKdi8A5O9hp4t3dBIdOanaup2UEL4lp7izzgt2rkMeuyQ1ZjHsN7L
|
15
|
+
mDsfjoFcYx/8CID9LBwRCN2p+YCuoWUjuorrdU/2eit2lNh6ypiF6WECAQOjgZAw
|
16
|
+
gY0wHQYDVR0OBBYEFIT65b/ekUlm38WKUsOzt7MgHMdtMA4GA1UdDwEB/wQEAwIB
|
17
|
+
BjASBgNVHRMBAf8ECDAGAQH/AgEBMEgGA1UdHwRBMD8wPaA7oDmGN2h0dHA6Ly9v
|
18
|
+
bmxpbmUuZGFuc2tlYmFuay5jb20vcGtpL0RCR1JPT1RfMTExMTExMDAwMi5jcmww
|
19
|
+
DQYJKoZIhvcNAQELBQADggEBAFjnBPCos7jMMLc3FqyQUMt/HJGKgJDrhYiPZBo9
|
20
|
+
njGkH52Urryqw1sbT3wXA1NuzbjHE3xTUD+5jNPCncYqML9xqQjSQkBcb9eJfHZ+
|
21
|
+
asiclsO38cSn2qriJPIrCREPOpRVqrGQRbZQhmDiB198hpAdLp38khJon/gXbR7u
|
22
|
+
9e0rN8MIM4sXn+lFuQIWiPuv+3llGSoLlIxJnjiQQ9FDjhwN5U+N1N2aHaLc5AHu
|
23
|
+
4X/qRutLCy7AYUJZMPBoakPLscYceW2Ztvx4VAyOXgHDdvmz0Bd58XWOs1A9bNMZ
|
24
|
+
FeYAB14D9yQRCkXYLhr6sm8HuyqaIkGChFpNb+Gf8gcPvtw=
|
25
|
+
-----END CERTIFICATE-----
|
data/lib/sepa/client.rb
CHANGED
@@ -17,10 +17,10 @@ module Sepa
|
|
17
17
|
:status,
|
18
18
|
:pin,
|
19
19
|
:signing_private_key,
|
20
|
-
:
|
20
|
+
:own_signing_certificate,
|
21
21
|
:signing_csr,
|
22
22
|
:encryption_private_key,
|
23
|
-
:
|
23
|
+
:bank_encryption_certificate,
|
24
24
|
:encryption_csr
|
25
25
|
|
26
26
|
BANKS = [:nordea, :danske]
|
@@ -39,7 +39,6 @@ module Sepa
|
|
39
39
|
validate :check_content
|
40
40
|
validate :check_pin
|
41
41
|
validate :check_command
|
42
|
-
validate :check_wsdl
|
43
42
|
validate :check_keys
|
44
43
|
validate :check_encryption_certificate
|
45
44
|
validate :check_encryption_cert_request
|
@@ -63,6 +62,8 @@ module Sepa
|
|
63
62
|
end
|
64
63
|
|
65
64
|
def environment=(value)
|
65
|
+
return unless value.respond_to? :downcase
|
66
|
+
|
66
67
|
@environment = value.downcase.to_sym
|
67
68
|
end
|
68
69
|
|
@@ -79,6 +80,7 @@ module Sepa
|
|
79
80
|
client = Savon.client(wsdl: wsdl)
|
80
81
|
|
81
82
|
begin
|
83
|
+
error = nil
|
82
84
|
response = client.call(command, xml: soap)
|
83
85
|
response &&= response.to_xml
|
84
86
|
rescue Savon::Error => e
|
@@ -86,19 +88,7 @@ module Sepa
|
|
86
88
|
error = e.to_s
|
87
89
|
end
|
88
90
|
|
89
|
-
|
90
|
-
response: response,
|
91
|
-
error: error,
|
92
|
-
command: command
|
93
|
-
}
|
94
|
-
options[:encryption_private_key] = rsa_key(encryption_private_key) if encryption_private_key
|
95
|
-
|
96
|
-
case bank
|
97
|
-
when :nordea
|
98
|
-
NordeaResponse.new options
|
99
|
-
when :danske
|
100
|
-
DanskeResponse.new options
|
101
|
-
end
|
91
|
+
initialize_response(error, response)
|
102
92
|
end
|
103
93
|
|
104
94
|
private
|
@@ -125,24 +115,40 @@ module Sepa
|
|
125
115
|
# Returns path to WSDL file
|
126
116
|
def wsdl
|
127
117
|
case bank
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
else
|
138
|
-
file = "wsdl_danske.xml"
|
139
|
-
end
|
118
|
+
when :nordea
|
119
|
+
if command == :get_certificate
|
120
|
+
file = "wsdl_nordea_cert.xml"
|
121
|
+
else
|
122
|
+
file = "wsdl_nordea.xml"
|
123
|
+
end
|
124
|
+
when :danske
|
125
|
+
if [:get_bank_certificate, :create_certificate].include? command
|
126
|
+
file = "wsdl_danske_cert.xml"
|
140
127
|
else
|
141
|
-
|
128
|
+
file = "wsdl_danske.xml"
|
129
|
+
end
|
142
130
|
end
|
143
131
|
|
144
132
|
"#{WSDL_PATH}/#{file}"
|
145
133
|
end
|
146
134
|
|
135
|
+
def initialize_response(error, response)
|
136
|
+
options = {
|
137
|
+
response: response,
|
138
|
+
error: error,
|
139
|
+
command: command
|
140
|
+
}
|
141
|
+
if encryption_private_key && !encryption_private_key.empty?
|
142
|
+
options[:encryption_private_key] = rsa_key(encryption_private_key)
|
143
|
+
end
|
144
|
+
|
145
|
+
case bank
|
146
|
+
when :nordea
|
147
|
+
NordeaResponse.new options
|
148
|
+
when :danske
|
149
|
+
DanskeResponse.new options
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
147
153
|
end
|
148
154
|
end
|
data/lib/sepa/error_messages.rb
CHANGED
@@ -12,5 +12,16 @@ module Sepa
|
|
12
12
|
STATUS_ERROR_MESSAGE = 'Status is required for this command and must be either NEW, DOWNLOADED or ALL'
|
13
13
|
FILE_REFERENCE_ERROR_MESSAGE = 'File reference is required for this command and must be under 33 characters'
|
14
14
|
ENCRYPTION_PRIVATE_KEY_ERROR_MESSAGE = 'Encryption private key is needed for this bank and this command'
|
15
|
+
NOT_OK_RESPONSE_CODE_ERROR_MESSAGE = 'The response from the bank suggested there was ' \
|
16
|
+
'something wrong with your request, check your parameters and try again'
|
17
|
+
|
18
|
+
DECRYPTION_ERROR_MESSAGE = 'The response could not be decrypted with the private key ' \
|
19
|
+
'that you gave. Check that the key is the private key of your own encryption certificate'
|
20
|
+
|
21
|
+
HASH_ERROR_MESSAGE = 'The hashes in the response did not match which means that the data in ' \
|
22
|
+
'the response is not intact'
|
23
|
+
|
24
|
+
SIGNATURE_ERROR_MESSAGE = 'The signature in the response did not verify and the response ' \
|
25
|
+
'cannot be trusted'
|
15
26
|
end
|
16
27
|
end
|
data/lib/sepa/response.rb
CHANGED
@@ -2,13 +2,16 @@ module Sepa
|
|
2
2
|
class Response
|
3
3
|
include ActiveModel::Validations
|
4
4
|
include Utilities
|
5
|
+
include ErrorMessages
|
5
6
|
|
6
7
|
attr_reader :soap, :error, :command
|
7
8
|
|
8
|
-
validates :soap, presence: true
|
9
|
-
validate :validate_document_format
|
10
9
|
validate :document_must_validate_against_schema
|
11
10
|
validate :client_errors
|
11
|
+
validate :response_code_is_ok
|
12
|
+
validate :validate_hashes
|
13
|
+
validate :verify_signature
|
14
|
+
validate :verify_certificate
|
12
15
|
|
13
16
|
def initialize(hash = {})
|
14
17
|
@soap = hash[:response]
|
@@ -41,7 +44,7 @@ module Sepa
|
|
41
44
|
end
|
42
45
|
|
43
46
|
if options[:verbose]
|
44
|
-
puts "These digests failed to verify: #{unverified_digests}
|
47
|
+
puts "These digests failed to verify: #{unverified_digests}"
|
45
48
|
end
|
46
49
|
|
47
50
|
false
|
@@ -50,15 +53,7 @@ module Sepa
|
|
50
53
|
# Verifies the signature by extracting the public key from the certificate
|
51
54
|
# embedded in the soap header and verifying the signature value with that.
|
52
55
|
def signature_is_valid?
|
53
|
-
|
54
|
-
|
55
|
-
node = canonicalize_exclusively node
|
56
|
-
|
57
|
-
signature = doc.at('xmlns|SignatureValue', xmlns: DSIG).content
|
58
|
-
|
59
|
-
signature = decode(signature)
|
60
|
-
|
61
|
-
certificate.public_key.verify(OpenSSL::Digest::SHA1.new, signature, node)
|
56
|
+
validate_signature(doc, certificate, :exclusive)
|
62
57
|
end
|
63
58
|
|
64
59
|
# Gets the application response from the response as an xml document
|
@@ -121,6 +116,11 @@ module Sepa
|
|
121
116
|
|
122
117
|
def ca_certificate; end
|
123
118
|
|
119
|
+
def response_code
|
120
|
+
node = doc.at('xmlns|ResponseCode', xmlns: BXD)
|
121
|
+
node.content if node
|
122
|
+
end
|
123
|
+
|
124
124
|
private
|
125
125
|
|
126
126
|
# Finds all reference nodes with digest values in the document and returns
|
@@ -154,21 +154,17 @@ module Sepa
|
|
154
154
|
nodes
|
155
155
|
end
|
156
156
|
|
157
|
-
def validate_document_format
|
158
|
-
unless doc.respond_to?(:canonicalize)
|
159
|
-
errors.add(:base, 'Document must be a valid XML file')
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
157
|
def document_must_validate_against_schema
|
164
|
-
return if command.to_sym == :get_bank_certificate
|
158
|
+
return if @error || command.to_sym == :get_bank_certificate
|
165
159
|
|
166
160
|
check_validity_against_schema(doc, 'soap.xsd')
|
167
161
|
end
|
168
162
|
|
169
163
|
def extract_application_response(namespace)
|
170
164
|
ar_node = doc.at('xmlns|ApplicationResponse', xmlns: namespace)
|
171
|
-
|
165
|
+
if ar_node
|
166
|
+
decode(ar_node.content)
|
167
|
+
end
|
172
168
|
end
|
173
169
|
|
174
170
|
def client_errors
|
@@ -180,5 +176,31 @@ module Sepa
|
|
180
176
|
doc.at("[xmlns|Id='#{uri}']", xmlns: OASIS_UTILITY)
|
181
177
|
end
|
182
178
|
|
179
|
+
def response_code_is_ok
|
180
|
+
return if @error
|
181
|
+
|
182
|
+
unless %w(00 24).include? response_code
|
183
|
+
errors.add(:base, NOT_OK_RESPONSE_CODE_ERROR_MESSAGE)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def validate_hashes
|
188
|
+
unless hashes_match?
|
189
|
+
errors.add(:base, HASH_ERROR_MESSAGE)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def verify_signature
|
194
|
+
unless signature_is_valid?
|
195
|
+
errors.add(:base, SIGNATURE_ERROR_MESSAGE)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def verify_certificate
|
200
|
+
unless certificate_is_trusted?
|
201
|
+
errors.add(:base, 'The certificate in the response is not trusted')
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
183
205
|
end
|
184
206
|
end
|
data/lib/sepa/soap_builder.rb
CHANGED
@@ -6,23 +6,23 @@ module Sepa
|
|
6
6
|
|
7
7
|
# SoapBuilder creates the SOAP structure.
|
8
8
|
def initialize(params)
|
9
|
-
@bank
|
10
|
-
@
|
11
|
-
@command
|
12
|
-
@content
|
13
|
-
@customer_id
|
14
|
-
@
|
15
|
-
@environment
|
16
|
-
@file_reference
|
17
|
-
@file_type
|
18
|
-
@language
|
19
|
-
@signing_private_key
|
20
|
-
@status
|
21
|
-
@target_id
|
22
|
-
|
23
|
-
@application_request
|
24
|
-
@header_template
|
25
|
-
@template
|
9
|
+
@bank = params[:bank]
|
10
|
+
@own_signing_certificate = params[:own_signing_certificate]
|
11
|
+
@command = params[:command]
|
12
|
+
@content = params[:content]
|
13
|
+
@customer_id = params[:customer_id]
|
14
|
+
@bank_encryption_certificate = params[:bank_encryption_certificate]
|
15
|
+
@environment = params[:environment]
|
16
|
+
@file_reference = params[:file_reference]
|
17
|
+
@file_type = params[:file_type]
|
18
|
+
@language = params[:language]
|
19
|
+
@signing_private_key = params[:signing_private_key]
|
20
|
+
@status = params[:status]
|
21
|
+
@target_id = params[:target_id]
|
22
|
+
|
23
|
+
@application_request = ApplicationRequest.new params
|
24
|
+
@header_template = load_header_template
|
25
|
+
@template = load_body_template SOAP_TEMPLATE_PATH
|
26
26
|
|
27
27
|
find_correct_bank_extension
|
28
28
|
end
|
@@ -89,13 +89,13 @@ module Sepa
|
|
89
89
|
set_node(@header_template, 'wsu|Created', iso_time)
|
90
90
|
set_node(@header_template, 'wsu|Expires', (Time.now.utc + 300).iso8601)
|
91
91
|
|
92
|
-
timestamp_id =
|
92
|
+
timestamp_id = set_node_id(@header_template, OASIS_UTILITY, 'Timestamp', 0)
|
93
93
|
|
94
94
|
timestamp_digest = calculate_digest(@header_template, 'wsu|Timestamp')
|
95
95
|
dsig = "dsig|Reference[URI='##{timestamp_id}'] dsig|DigestValue"
|
96
96
|
set_node(@header_template, dsig, timestamp_digest)
|
97
97
|
|
98
|
-
body_id =
|
98
|
+
body_id = set_node_id(@template, ENVELOPE, 'Body', 1)
|
99
99
|
|
100
100
|
body_digest = calculate_digest(@template, 'env|Body')
|
101
101
|
dsig = "dsig|Reference[URI='##{body_id}'] dsig|DigestValue"
|
@@ -104,7 +104,7 @@ module Sepa
|
|
104
104
|
signature = calculate_signature(@header_template, 'dsig|SignedInfo')
|
105
105
|
set_node(@header_template, 'dsig|SignatureValue', signature)
|
106
106
|
|
107
|
-
formatted_cert = format_cert(@
|
107
|
+
formatted_cert = format_cert(@own_signing_certificate)
|
108
108
|
set_node(@header_template, 'wsse|BinarySecurityToken', formatted_cert)
|
109
109
|
end
|
110
110
|
|
@@ -115,23 +115,5 @@ module Sepa
|
|
115
115
|
@header_template.at('wsse|Reference')['URI'] = "##{security_token_id}"
|
116
116
|
end
|
117
117
|
|
118
|
-
def set_timestamp_id
|
119
|
-
timestamp_id = "timestamp-#{SecureRandom.uuid}"
|
120
|
-
|
121
|
-
@header_template.at('wsu|Timestamp')['wsu:Id'] = timestamp_id
|
122
|
-
@header_template.css('dsig|Reference')[0]['URI'] = "##{timestamp_id}"
|
123
|
-
|
124
|
-
timestamp_id
|
125
|
-
end
|
126
|
-
|
127
|
-
def set_body_id
|
128
|
-
body_id = "body-#{SecureRandom.uuid}"
|
129
|
-
|
130
|
-
@template.at('env|Body')['wsu:Id'] = body_id
|
131
|
-
@header_template.css('dsig|Reference')[1]['URI'] = "##{body_id}"
|
132
|
-
|
133
|
-
body_id
|
134
|
-
end
|
135
|
-
|
136
118
|
end
|
137
119
|
end
|