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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sepa/application_request.rb +1 -1
  3. data/lib/sepa/application_response.rb +15 -16
  4. data/lib/sepa/attribute_checks.rb +18 -25
  5. data/lib/sepa/banks/danske/danske_response.rb +65 -8
  6. data/lib/sepa/banks/danske/soap_danske.rb +1 -1
  7. data/lib/sepa/banks/nordea/nordea_response.rb +11 -0
  8. data/lib/sepa/certificates/danske_root_certificate.cer +25 -0
  9. data/lib/sepa/client.rb +35 -29
  10. data/lib/sepa/error_messages.rb +11 -0
  11. data/lib/sepa/response.rb +42 -20
  12. data/lib/sepa/soap_builder.rb +20 -38
  13. data/lib/sepa/utilities.rb +36 -11
  14. data/lib/sepa/version.rb +1 -1
  15. data/lib/sepafm.rb +10 -1
  16. data/readme.md +64 -28
  17. data/test/sepa/banks/danske/danske_cert_response_test.rb +41 -4
  18. data/test/sepa/banks/danske/danske_cert_soap_builder_test.rb +1 -1
  19. data/test/sepa/banks/danske/danske_generic_soap_builder_test.rb +6 -17
  20. data/test/sepa/banks/danske/danske_response_test.rb +97 -0
  21. data/test/sepa/banks/danske/keys/bank_encryption_cert.pem +17 -17
  22. data/test/sepa/banks/danske/keys/bank_signing_cert.pem +17 -17
  23. data/test/sepa/banks/danske/responses/create_cert.xml +14 -14
  24. data/test/sepa/banks/danske/responses/download_file_list.xml +42 -0
  25. data/test/sepa/banks/danske/responses/get_bank_cert.xml +14 -36
  26. data/test/sepa/banks/danske/responses/get_bank_certificate_not_ok.xml +2 -0
  27. data/test/sepa/banks/nordea/nordea_application_request_test.rb +3 -5
  28. data/test/sepa/banks/nordea/nordea_application_response_test.rb +25 -30
  29. data/test/sepa/banks/nordea/nordea_generic_soap_builder_test.rb +2 -2
  30. data/test/sepa/banks/nordea/nordea_response_test.rb +83 -17
  31. data/test/sepa/banks/nordea/responses/df_ktl.xml +20 -44
  32. data/test/sepa/banks/nordea/responses/dfl.xml +7 -6
  33. data/test/sepa/banks/nordea/responses/download_file_list_no_content.xml +21 -0
  34. data/test/sepa/banks/nordea/responses/gc.xml +21 -49
  35. data/test/sepa/banks/nordea/responses/invalid/body_altered.xml +21 -0
  36. data/test/sepa/banks/nordea/responses/invalid/timestamp_altered.xml +21 -0
  37. data/test/sepa/banks/nordea/responses/not_ok_response_code.xml +21 -0
  38. data/test/sepa/banks/nordea/responses/uf.xml +7 -6
  39. data/test/sepa/client_test.rb +134 -35
  40. data/test/sepa/fixtures.rb +8 -8
  41. data/test/sepa/sepa_test.rb +1 -1
  42. data/test/test_helper.rb +5 -1
  43. metadata +18 -6
  44. data/test/sepa/banks/nordea/responses/gbc.xml +0 -15
  45. /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: ef6bfed9365908016a49c134f04b31270f2cd7c0
4
- data.tar.gz: fad409e18066c845c6cc8c267d1d6f328c365f6a
3
+ metadata.gz: b12a4088afb4ab54adbbab5c077ffabed2637060
4
+ data.tar.gz: 677279058018c696a4f5e6d0b2e47a82527213ca
5
5
  SHA512:
6
- metadata.gz: 0caa72426efa99e3baf93ad1cbdeb08ae742a99001de93068d48a8405a406eb3fb07eeb5ae69aa3b582f1af7c6a53f2a1153eca7d1c5b4b68c0798a217a4c082
7
- data.tar.gz: 05e7942b179477e1742190c6160bf6270b8770fe1ee3e78ac5be7926d2d39464dc87b3b0ea570cb03d60653d19f4cd2b2593c31001f6be94de00f9ab7e14acf0
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(@signing_certificate))
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
- node = doc.at('xmlns|SignedInfo', 'xmlns' => DSIG)
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
- private
55
-
56
- def validate_document_format
57
- unless doc.respond_to?(:canonicalize)
58
- errors.add(:base, 'Document must be a valid XML file')
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
- end
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
- OpenSSL::X509::Certificate.new signing_certificate
31
+ x509_certificate own_signing_certificate
32
32
  rescue
33
- errors.add(:signing_certificate, "Invalid signing certificate")
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 command == :create_certificate
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
- if file_type.nil? || file_type.size > 35
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
- if send(attribute).nil? || send(attribute).size > length
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) if content.nil?
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 command == :create_certificate
86
+ return unless [:create_certificate, :get_certificate].include? command
99
87
 
100
- check_presence_and_length(:pin, 10, PIN_ERROR_MESSAGE)
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
- errors.add(:encryption_certificate, ENCRYPTION_CERT_ERROR_MESSAGE) unless encryption_certificate
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
- unless file_reference && file_reference.length <= 32
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
- node = doc.at("[xml|id='#{uri}']")
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
- encrypted_application_response = extract_application_response(BXD)
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
- .css('CipherValue', 'xmlns' => XMLENC)[1]
69
- .content
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 = OpenSSL::X509::Certificate.new(@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
- :signing_certificate,
20
+ :own_signing_certificate,
21
21
  :signing_csr,
22
22
  :encryption_private_key,
23
- :encryption_certificate,
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
- options = {
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
- when :nordea
129
- if command == :get_certificate
130
- file = "wsdl_nordea_cert.xml"
131
- else
132
- file = "wsdl_nordea.xml"
133
- end
134
- when :danske
135
- if [:get_bank_certificate, :create_certificate].include? command
136
- file = "wsdl_danske_cert.xml"
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
- return nil
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
@@ -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
- node = doc.at('xmlns|SignedInfo', xmlns: DSIG)
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
- decode(ar_node.content)
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
@@ -6,23 +6,23 @@ module Sepa
6
6
 
7
7
  # SoapBuilder creates the SOAP structure.
8
8
  def initialize(params)
9
- @bank = params[:bank]
10
- @signing_certificate = params[:signing_certificate]
11
- @command = params[:command]
12
- @content = params[:content]
13
- @customer_id = params[:customer_id]
14
- @encryption_certificate = params[: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
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 = set_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 = set_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(@signing_certificate)
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