sepafm 0.1.3 → 0.1.4

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