sepafm 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|